... where by any assembly I mean also assemblies that depend on assemblies which are wrappers of native libraries.
The Minimal, Reproducible Example for this question is here:
https://gitlab.com/mikenakis-public/dotnet/playground/loadingnativelibraries
I have a library called MyLib. It exports MyType, which contains one method: void DoYourThing(). This method uses some type from the LibGit2Sharp package, so for this reason, MyLib.csproj contains <PackageReference Include="LibGit2Sharp" Version="0.29.0" />. Not that it matters, but here is the code:
public static void DoYourThing() { string currentDirectory = SysIo.Directory.GetCurrentDirectory(); Sys.Console.WriteLine( $"Current Directory: {currentDirectory}" ); string repositoryDirectory = LibGit2Sharp.Repository.Discover( currentDirectory ); Sys.Console.WriteLine( $"Repository Directory: {repositoryDirectory}" ); bool valid = LibGit2Sharp.Repository.IsValid( repositoryDirectory ); Sys.Console.WriteLine( $"Is a valid repository: {valid.ToString().ToUpperInvariant()}" ); } It just so happens that the LibGit2Sharp package is a managed wrapper around a native library, but I have no say on this and no control over this, and I would like my code to not have the slightest knowledge of this.
Then I have an application called DirectDependencyApp which has a dependency on MyLib, and contains the statement MyType.DoYourThing();, proving that the whole thing works.
Now, I have an application called DynamicLoadingApp which depends on nothing. Instead, it accepts a DLL path-name as a command-line argument, it loads it as an assembly, it finds a type with a DoYourThing method, and it invokes that method. Not that it matters, but here is the code:
string assemblyFilePath = SysIo.Path.GetFullPath( arguments.Single() ); SysReflect.Assembly assembly = SysReflect.Assembly.LoadFrom( assemblyFilePath ); SysReflect.MethodInfo methodInfo = assembly.GetTypes().Single().GetMethod( "DoYourThing" ) ?? throw new Sys.Exception(); methodInfo.Invoke( null, null ); (Disclosure: what I am actually doing is writing a test-runner like VSTest.Console.exe: it discovers all projects within a solution that produce VisualStudio-UnitTesting-compatible test assemblies, and runs them. DynamicLoadingApp corresponds to the test-runner, MyLib corresponds to a test assembly, and LibGit2Sharp corresponds to an assembly-under-test, or a library that the assembly-under-test depends on. It is crucial to note that the test-runner wants to know nothing specific about each test assembly, and nothing at all about each assembly-under-test. Furthermore, the test assemblies also want to know absolutely nothing about any particular test-runner.)
For DynamicLoadingApp to be able to load MyLib, I already had to do something that I am not quite happy with: I had to add <CopyLocalLockFileAssemblies>true to MyLib.csproj, or else DynamicLoadingApp would fail while trying to load MyLib because LibGit2Sharp.dll could not be found. I am not happy with this, but let's ignore that for now, it might be the subject of another question. The fact is that by adding <CopyLocalLockFileAssemblies>true I got past that hurdle. Unfortunately, that's when I arrived at the main hurdle.
When DynamicLoadingApp runs, it successfully finds MyLib, and it successfully loads it, (which means that LibGit2Sharp gets successfully loaded,) and it successfully invokes the DoYourThing method, but as soon as the method tries to actually invoke any method of LibGit2Sharp, the whole thing blows up with:
System.TypeInitializationException: 'The type initializer for 'LibGit2Sharp.Core.NativeMethods' threw an exception.' The inner exception is:
DllNotFoundException: Unable to load DLL 'git2-a2bde63' or one of its dependencies: The specified module could not be found. (0x8007007E) This exception was originally thrown at this call stack:
LibGit2Sharp.Core.NativeMethods.InitializeNativeLibrary() LibGit2Sharp.Core.NativeMethods.NativeMethods() The call stack shown in Visual Studio is this:
LibGit2Sharp.Core.Proxy.git_repository_discover.AnonymousMethod__0({LibGit2Sharp.Core.Handles.GitBuf}) Unknown LibGit2Sharp.Core.Proxy.ConvertPath({Method = {System.Reflection.RuntimeMethodInfo}}) Unknown LibGit2Sharp.Core.Proxy.git_repository_discover({LibGit2Sharp.Core.FilePath}) Unknown LibGit2Sharp.Repository.Discover("D:\\Personal\\LoadingNativeLibraries") Unknown MyLib.MyType.DoYourThing() C# So, what is happening is that GitLib2Sharp, which is a library that I have absolutely no control over, is failing to locate its own native libraries. Of course this problem is not specific to GitLib2Sharp, it pertains to any wrapper of native libraries.
I know there are workarounds for fixing the problem by copying the native binaries from bin/Debug/net8.0/runtimes/win-x64 to bin/Debug/net8.0, or by using some MSBuild property which will put them directly there, but I want a proper solution so that I can give DynamicLoadingApp to people and tell them "here, use it" without having to tell them "before you can use it you have to modify each and every one of your MyLibs to accommodate DynamicLoadingApp's quirks".
So:
How can I get DynamicLoadingApp to load MyLib and have MyLib successfully invoke Gitlib2Sharp?
The solution must involve modifications only to DynamicLoadingApp. (Not modifications to MyLib, which works as it is.)
<CopyLocalLockFileAssemblies>truein MyLib's project file causes all binaries (including those underruntimes) to be emitted into MyLib'sbindirectory. And althoughMyLibsucceeds in loadingGitLib2Sharpfrom thatbindirectory, it fails to locate the stuff underruntimes. Plus, I have already stated that I did not really want to be doing even this. In any case, I added a link to a cloneable MRE, so you can try any idea with it.MyLibdependent onLibGit2Sharp, how to ensure thatLibGit2Sharpcan be loaded successfully should be a concern forMyLib(or forDirectDependencyApp). Alternatively, your test runner may accept some search path like options to complete this task.MyLibworks fine, asDirectDependencyAppproves. Things get weird when it is dynamically loaded byDynamicLoadingApp, so it isDynamicLoadingAppthat must take whatever measures are necessary in order to load it properly.