natemcmaster / DotNetCorePlugins

.NET Core library for dynamically loading code

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

DllNotFoundException loading my native DLL

timreynolds-53 opened this issue · comments

We have a native DLL that we have a C# wrapper for. Our C# wrapper is a local Nuget Package which we host in our own private repo (for code sharing among projects). Our specs call for allowing "dynamically determined" plugins for specific behavior based on different "partners". Your package clears up a lot of issues we had trying to roll our own, thanks.
Our problem is that when a partner wishes to use our NuGet package with our native DLL, it gets a not found issue. I have checked, and the native dll is in the "plugin" directory, but when I try to access it I get the exception. If I move the native DLL up to the main directory for the "mother" app, it works fine.
This is not a long term solution since we occasionally upgrade our native DLL, which sometimes introduces breaking changes. We cant require our partners to upgrade their "plugins" each time we release a new DLL.
I really need to allow the version of the DLL that a partner is using to remain in their plugin folder and be separate from any other partner that may be running a different version.
My load code in my program.cs is:

   #region My Code
    var loaders = GetPluginLoaders(adapterBaseLib);
    foreach (var loader in loaders)
    {
           foreach (var pluginType in loader
                 .LoadDefaultAssembly()                             
                 .GetTypes()
                 .Where(t => typeof(IAdapterRegistration).IsAssignableFrom(t) && !t.IsAbstract))
           {
                // This assumes the implementation of IAdapterConfigurator has a parameterless constructor
                var plugin = Activator.CreateInstance(pluginType) as IAdapterRegistration;
                plugin?.Configure(services);
            }
     }
 #endregion



    #region GetPluginLoaders
    private static List<PluginLoader> GetPluginLoaders(string adapterBaseLib)
    {
        var loaders = new List<PluginLoader>();

        // create plugin loaders
        var pluginsDir = Path.Combine(adapterBaseLib, "plugins");
        foreach (var dir in Directory.GetDirectories(pluginsDir))
        {
            var dirName = Path.GetFileName(dir);
            var pluginDll = Path.Combine(dir, dirName + ".dll");
            if (File.Exists(pluginDll))
            {
                var loader = PluginLoader.CreateFromAssemblyFile(
                    pluginDll,                         
                    sharedTypes: new[] 
                    {
                        typeof(IAdapterRegistration), // The IAdapterRegistration class lets plugins get registered.
                        typeof(IServiceCollection),   // Required for the Adapter to register itself
                        typeof(IWebHelper),           // A class that the main program wants the plugins to have access to
                        typeof(IEncryptionHelper),    // Another class that the main program wants the plugins to have access to
                        typeof(ICommanderManager),    // Class needed to manage MTE/Commander
                        typeof(IInitialValuesHelper), // Class needed to call out for MTE Magic Values
                    });                     
                loaders.Add(loader);
            }
        }
        return loaders;
    } 
    #endregion

I can easily inject my ICommanderHelper (which is from our NuGet package) into a test plugin and the plugin finds it. I can debug down to the line that actually tries to do the DllImport of our native DLL, and that is where it crashes. Again, If I move the native DLL up to the "root folder" it works fine, but then I am stuck with the versioning issue.

I see from your docs that you have solved the "load native dll" problem for plugins, but I cant figure out what I would need to do to ensure they are loaded from the specific plugin folder unique for each plugin.

Any suggestions?

You have to setup your NuGet package properly to make it work.

Here's the example:
enet-nuget
With this setup native libraries will be copied properly by .NET toolchain and loaded just fine by the runtime in published applications.

Thanks, but that is not exactly my issue.
I have a main app with a Plugins folder underneath it.
I have a native dll that the plugin must load.
If the native dll is in the main app folder, it loads fine
If the native dll is in the specific plugins folder (for example SamplePlugin) it fails to load.
In other words, when the plugin tries to do a PInvoke on the native lib, it cannot find it unless it is in the main apps folder.

This is my project structure:
Capture1

This is the listing of files in the main folder:
Capture2

This is the listing of my "Sample Plugin" folder:
Capture3

The PluginWrapper is a helper class to provide the needed Interface files for the custom plugin.
If I have the myNativeLib.v2 in the main folder everything works.
If I have the myNativeLib.v2 in the SamplePlugin folder, it cannot be found.
We need to keep the myNativeLib.vX in the plugin folder, since different plugins may need specific versions of the native library.
Your documentation seems to state that loading native dlls works fine, but for us, they must be in the plugin folder.
Any thoughts?

Sorry, did not mean to close this...

The native libraries themselves are handled with a regular P/Invoke using DllImport or some specific API?

The "SamplePlugin" has a class within it "PluginNativeHelper.cs" that does the P/Invoke. So it is loaded directly from the SamplePlugin. Our "partners" are given a spec that they write a plugin to. The plugin only has one requirement and that is to implement the Interface in the PluginWrapper. There is only one method "Exec(string command, string data)".
The data can be anything, but generally it is a Json String so that the originating endpoint just packages up its data into a class, and serializes it to Json prior to sending it to our web api which then dynamically loads and invokes the "plugin". The use of our native library to do some "processing" on the incoming data is optional, but several partners wish to use it. If so, they get guidance from us as to what the P/Invoke signature must look like.

Can you share the SamplePlugin.deps.json file? (preferably via gist.github.com or uploaded instead of inline as a comment...it can be a very large json file)

SampleAdapter.deps.zip

Here is the file from the actual project that is not working (not exactly the screen shots i put together earlier)

I'm not sure if this is still a problem for you, but I've been able to solve it for my native libraries (Microsoft.Data.SQLClient) and a few others by changing the plugins to the netcoreapp3.1 output type. I wasn't able to load a native dll from a netstandard2.0 plugin without it being in the host applications main directory. I didn't dig too far into the dep.json files, as this was the second solution I tried and it has worked for me.

Thanks, we switched to .Net 5.0 and no longer see this as an issue. This sounds remarkably close to what you discovered.