加载和解析非托管库
ALC也能够加载和解析本地库,当你调用一个标有[DllImport]属性的外部方法时,会触发本地解析:
[DllImport("SomeNativeLibrary.dll")]
static extern int SomeNativeMethod (string text);
因为我们没有在[DllImport]属性中指定完整的路径,所以调用Some NativeMethod会在包含SomeNativeMethod定义的程序集的任何ALC中触发解析。
ALC中的虚拟(virtual)解析方法被称为LoadUnmanagedDll,而加载方法被称为LoadUnmanagedDllFromPath
protected override IntPtr LoadUnmanagedDll (string unmanagedDllName)
{
// Locate the full path of unmanagedDllName...
string fullPath = ...
return LoadUnmanagedDllFromPath (fullPath); // Load the DLL
}
如果无法定位该文件,可以返回IntPtr.Zero。然后CLR将触发ALC的ResolvingUnmanagedDll事件.
有趣的是,LoadUnmanagedDllFromPath方法是受保护的,所以通常不能从ResolvingUnmanagedDll事件处理器中调用它。然而,可以通过调用静态的NativeLibrary.Load来实现同样的结果。
someALC.ResolvingUnmanagedDll += (requestingAssembly, unmanagedDllName) =>
{
return NativeLibrary.Load ("(full path to unmanaged DLL)");
};
虽然本地库通常由ALC解析和加载,但它们并不 "属于 "ALC。在加载后,本地库独立存在,并负责解决它可能具有的任何横向依赖关系。
此外,本地库对进程是全局性的,所以如果一个本地库有相同的文件名,就不可能加载两个不同版本的本地库。
AssemblyDependencyResolver
在C#插件式开发——详解默认ALC和当前ALC的区别(AssemblyLoadContext)_zxu_er的博客-CSDN博客, 我们说,默认的ALC会读取.deps.json和.runtimeconfig.json文件(如果存在的话),以确定在哪里找到特定平台和“开发时”的NuGet依赖关系
如果要将程序集加载到具有特定于平台或 NuGet 依赖项的自定义 ALC 中,则需要以某种方式重现此逻辑。
可以通过解析配置文件并仔细遵循特定于平台的名字对象的规则来完成此操作,但这样做不仅困难,而且如果规则在 .NE 的更高版本中发生变化,编写的代码也会破坏。
AssemblyDependencyResolver类会解决上面问题。要使用它,你要将探测路径传给它:
var resolver = new AssemblyDependencyResolver (@"c:\temp\foo.dll");
然后,为了找到一个依赖关系的路径,你调用ResolveAssemblyToPath方法。
string path = resolver.ResolveAssemblyToPath (new AssemblyName ("bar"));
在没有 .deps.json 文件的情况下(或者如果 .deps.json 不包含与 bar.dll 相关的任何内容),这将被计算为 c:\temp\bar.dll。
你同样可以通过调用ResolveUnmanaged DllToPath来解决非托管的依赖关系。
说明更复杂场景的一种好方法是创建一个名为 ClientApp 的新控制台项目,然后添加对Microsoft.Data.SqlClient 的 NuGet 引用,然后添加下列代码:
using Microsoft.Data.SqlClient;
namespace ClientApp
{
public class Program
{
public static SqlConnection GetConnection() => new SqlConnection();
static void Main() => GetConnection(); // Test that it resolves
}
}
现在构建应用程序,并在输出文件夹中查看,会看到一个名为Microsoft.Data.SqlClient.dll的文件。然而,这个文件在运行时从未加载,并试图显式加载它会引发异常。
实际加载的程序集位于runtimes\win(或runtimes/unix)子文件夹中;默认的 ALC 会去加载它,因为它解析 ClientApp.deps.json 文件。
如果您尝试从另一个应用程序加载 ClientApp.dll 程序集,则需要编写一个 ALC 来解析其依赖项 Microsoft.Data.SqlClient.dll。
这样做时,仅查看 ClientApp.dll 所在的文件夹是不够的,相反,你需要使用AssemblyDependencyResolver来确定该文件在使用的平台上的位置。
string path = @"C:\source\ClientApp\bin\Debug\netcoreapp3.0\ClientApp.dll";
var resolver = new AssemblyDependencyResolver (path);
var sqlClient = new AssemblyName ("Microsoft.Data.SqlClient");
Console.WriteLine (resolver.ResolveAssemblyToPath (sqlClient));
在 Windows 机器上,这会输出以下内容:
C:\source\ClientApp\bin\Debug\netcoreapp3.0\runtimes\win\lib\netcoreapp2.1\Microsoft.Data.SqlClient.dll
卸载ALCs
在简单的情况下,可以卸载非默认的 AssemblyLoadContext,释放内存并释放它加载的程序集上的文件锁。
为此,必须使用 isCollectible 参数 true 实例化 ALC:
var alc = new AssemblyLoadContext ("test", isCollectible:true);
然后你可以调用ALC上的Unload方法来启动卸载过程。
unload方法
卸载模型是合作的而不是抢先的。如果任何 ALC 程序集中的任何方法正在执行,卸载将被推迟,直到这些方法完成。
实际的卸载发生在垃圾回收期间;如果ALC外部的任何东西对ALC内部的任何东西(包括对象、类型和程序集)有任何(非弱)的引用,它将不会触发卸载。
API(包括 .NET BCL 中的 API)在静态字段或字典中缓存对象或订阅事件并不少见——这使得创建阻止卸载的引用变得容易,特别是如果 ALC 中的代码以非平凡的方式使用其 ALC 之外的 API。
确定卸载失败的原因很困难,需要使用 WinDbg 等工具。