...其中,any程序集我指的是依赖于本机库包装程序集的程序集。
此问题的最小、可重现的示例在这里:
https://gitlab.com/mikenakis-public/dotnet/playground/loadingnativelibraries
我有一个名为
MyLib
的图书馆。它导出 MyType
,其中包含一种方法:void DoYourThing()
。此方法使用 LibGit2Sharp
包中的某种类型,因此,MyLib.csproj
包含 <PackageReference Include="LibGit2Sharp" Version="0.29.0" />
。这并不重要,但这是代码:
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()}" );
}
恰巧
LibGit2Sharp
包是本地库的托管包装器,但我对此没有发言权,也无法控制它,我希望我的代码对此一无所知。
然后我有一个名为
DirectDependencyApp
的应用程序,它依赖于 MyLib
,并包含语句 MyType.DoYourThing();
,证明整个事情有效。
现在,我有一个名为
DynamicLoadingApp
的应用程序,它不依赖任何东西。相反,它接受 DLL 路径名作为命令行参数,将其作为程序集加载,找到具有 DoYourThing
方法的类型,然后调用该方法。这并不重要,但这是代码:
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 );
(披露:我实际上正在编写一个像VSTest.Console.exe这样的测试运行程序:它发现解决方案中生成与VisualStudio-UnitTesting兼容的测试程序集的所有项目,并运行它们。
DynamicLoadingApp
对应于测试运行器,MyLib
对应于测试程序集,LibGit2Sharp
对应于被测程序集,或被测程序集所依赖的库。需要注意的是测试运行程序不想了解每个测试程序集的任何具体信息,也不想了解每个正在测试的程序集的任何具体信息。此外,测试程序集也完全不想了解任何特定测试运行程序。)
为了让
DynamicLoadingApp
能够加载MyLib
,我已经不得不做一些我不太满意的事情:我必须将<CopyLocalLockFileAssemblies>true
添加到MyLib.csproj
,否则DynamicLoadingApp
会在尝试时失败加载 MyLib
,因为找不到 LibGit2Sharp.dll
。我对此不满意,但现在让我们忽略它,这可能是另一个问题的主题。事实上,通过添加 <CopyLocalLockFileAssemblies>true
我克服了这个障碍。不幸的是,那是我遇到主要障碍的时候。
当
DynamicLoadingApp
运行时,它成功找到 MyLib
,并成功加载它(这意味着 LibGit2Sharp
已成功加载),并且它成功调用 DoYourThing
方法,但一旦该方法尝试实际上调用 LibGit2Sharp
的任何方法,整个事情都会爆炸:
System.TypeInitializationException:
'The type initializer for 'LibGit2Sharp.Core.NativeMethods' threw an exception.'
内部异常是:
DllNotFoundException:
Unable to load DLL 'git2-a2bde63' or one of its dependencies: The specified module could not be found.
(0x8007007E)
这个异常最初是在这个调用堆栈上抛出的:
LibGit2Sharp.Core.NativeMethods.InitializeNativeLibrary()
LibGit2Sharp.Core.NativeMethods.NativeMethods()
Visual Studio 中显示的调用堆栈是这样的:
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#
所以,正在发生的事情是,
GitLib2Sharp
,这是一个我完全无法控制的库,无法找到自己的本机库。当然,这个问题并不特定于GitLib2Sharp
,它与本机库的任何包装器有关。
我知道有解决问题的方法,可以通过将本机二进制文件从
bin/Debug/net8.0/runtimes/win-x64
复制到 bin/Debug/net8.0
,或者使用一些 MSBuild 属性将它们直接放在那里,但我想要一个正确的解决方案,以便我可以给出 DynamicLoadingApp
告诉人们“在这里,使用它”,而不必告诉他们“在使用它之前,您必须修改每一个 MyLib 以适应 DynamicLoadingApp 的怪癖”。
所以:
如何让
DynamicLoadingApp
加载 MyLib
并让 MyLib
成功调用 Gitlib2Sharp
?
解决方案必须仅涉及对
DynamicLoadingApp
的修改。 (不是对 MyLib
的修改,它按原样工作。)
所以,我找到了答案。它需要创建我自己的
AssemblyLoadContext
,它使用 AssemblyDependencyResolver
。这是完整的代码:
sealed class DynamicLoadingAppMain
{
static void Main( string[] arguments )
{
Sys.Console.WriteLine( nameof( DynamicLoadingAppMain ) );
string assemblyFilePath = SysIo.Path.GetFullPath( arguments.Single() );
SysLoader.AssemblyLoadContext assemblyLoadContext = new ResolvingAssemblyLoadContext( assemblyFilePath );
SysReflect.Assembly assembly = assemblyLoadContext.LoadFromAssemblyPath( assemblyFilePath );
SysReflect.MethodInfo methodInfo = assembly.GetTypes().Single().GetMethod( "DoYourThing" ) ?? throw new Sys.Exception();
methodInfo.Invoke( null, null );
Sys.Console.Write( "Press [Enter] to continue: " );
Sys.Console.ReadLine();
}
sealed class ResolvingAssemblyLoadContext : SysLoader.AssemblyLoadContext
{
readonly SysLoader.AssemblyDependencyResolver resolver;
public ResolvingAssemblyLoadContext( string assemblyFilePath )
{
resolver = new SysLoader.AssemblyDependencyResolver( assemblyFilePath );
}
protected override SysReflect.Assembly? Load( SysReflect.AssemblyName assemblyName )
{
string? assemblyPath = resolver.ResolveAssemblyToPath( assemblyName );
if( assemblyPath != null )
return LoadFromAssemblyPath( assemblyPath );
return null;
}
protected override nint LoadUnmanagedDll( string unmanagedDllName )
{
string? libraryPath = resolver.ResolveUnmanagedDllToPath( unmanagedDllName );
if( libraryPath != null )
return LoadUnmanagedDllFromPath( libraryPath );
return 0;
}
}
}
这是(光荣的!)输出:
DynamicLoadingAppMain
Current Directory: D:\Personal\LoadingNativeLibraries
Repository Directory: D:\Personal\LoadingNativeLibraries\.git\
Is a valid repository: TRUE
Press [Enter] to continue: