How do I change the lookup path for .NET libraries

2019-06-03 22:41发布

问题:

I developed a DLL in Managed C++ which loads some plugins (implemented in any .NET language) at runtime using System.Reflection.Assembly.LoadFile. The interface which is implemented by all plugins is implemented in C#. It's used by the Managed C++ code like this:

#using <IMyPluginInterface.dll>  // Make the 'IMyPluginInterface' type available

ref class PluginManager {
    List<IMyPluginInterface ^> ^m_plugins;

    // Load all plugins in a well-known directory.
    void load() {
        for ( string dllFile in Directory.GetFiles( .., "*.dll" ) ) {
            // Lookup the type of the plugin object using Reflection
            Type pluginType = ...;

            // Finally, instantiate the plugin and add it to our list.
            m_plugins.Add( (IMyPluginInterface ^)Activator.CreateInstance( pluginType ) );
        }
    }
}

Loading the plugins works well; the problem I'm facing is that at runtime, the IMyPlugnInterface.dll file might not be in the same directory as the Managed C++ DLL. This means that the 'IMyPluginInterface' type is not available at runtime, and an exception is thrown.

I previously asked whether it was maybe possible to influence the lookup path used when resolving DLLs referenced via the #using statement. Unfortunately, this didn't yield any result.

Is there maybe a different approach to this? Can types which are referenced via #using be compiled into the Managed C++ DLL? Maybe anybody else has an entirely different solution?

回答1:

You can use several options - if you know in advance where the assembly will be located, you can add that path to your application's configuration file:

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <probing privatePath="MyPath"/>
    </assemblyBinding>
  </runtime>
</configuration>

If you want to search for the assembly at runtime, you can implement a handler for AppDomain::CurrentDomain->AssemblyResolve event:

ref class AssemblyResolver
{
public:
    /// The path where the assemblies are searched
    property String^ Path
    {
        String^ get()
        { return path_; }
    }

    explicit AssemblyResolver(String^ path)
        : path_(path)
    { /* Void */ }

    Assembly^ ResolveHandler(Object^ sender, ResolveEventArgs^ args)    
    {
        // The name passed here contains other information as well
        String^ dll_name = args->Name->Substring(0, args->Name->IndexOf(','));
        String^ path = System::IO::Path::Combine(path_, dll_name+".dll");

        if ( File::Exists(path) )
            return Assembly::LoadFile(path);

        return nullptr;
    }

private:
    String^ path_;
};

and you can wire it using something like this:

AssemblyResolver^ resolver = gcnew AssemblyResolver(path);
AppDomain::CurrentDomain->AssemblyResolve += gcnew ResolveEventHandler(
    resolver, 
    &AssemblyResolver::ResolveHandler
);

Just make sure this is done before calling any methods that may use types from the assembly that has to be resolved.



回答2:

Is it not the standard .net assembly resolution strategy? See here for a detailed introduction.