在类库中处理AssemblyResolve事件的位置
本文关键字:事件 位置 AssemblyResolve 处理 类库 | 更新日期: 2023-09-27 18:22:04
我需要动态地将程序集引用从一个类库解析到另一个类库。类库是从PowerShell脚本加载的,因此在可执行文件中查找依赖程序集的默认.NET行为直接失败,因为可执行文件是PowerShell本身。如何使这些依赖程序集引用正确解析/工作?
详细信息:
我有两个实用程序库:一个是核心库,另一个是执行一些非常具体的解析任务的库。我希望在PowerShell脚本中动态加载它们,而不在GAC中安装它们。第二个库依赖于第一个库。在VS解决方案中,解析库有一个对核心库的项目引用,其中Copy Local
=true
。
使用(此处为PowerShell)后,我可以从解析库输出bin(/Debug |/Release)文件夹加载并使用这两个库:
[Reflection.Assembly]::LoadFile("C:'...thefile.dll")
但是,无论何时调用解析(依赖)库中的方法(该方法从核心库调用某些内容),都无法解析核心程序集。这…令人沮丧…因为文件在同一个文件夹中。一个或两个都有强名称键没有区别。
我现在的解决方法是处理AssemblyResolve
事件。棘手的事情是弄清楚把它放在类库的哪里,因为没有一个入口点总是像可执行的Main()
方法中那样在其他任何东西之前执行(请参阅c#中的类库是否有等效的Application_Start)。
目前,我已经创建了一个静态Resolver
类,该类具有一个附加AssemblyResolve
处理程序的静态构造函数,然后在每个解析类中都有一个静态构造函数,该构造函数引用静态解析器类,从而强制执行解析器类的静态构造函数。结果是AssemblyResolve事件只附加一次,并使用通用的中心代码进行处理。所以它是有效的。但我讨厌在我所有的解析类中添加一个时髦的静态构造函数。
有更好的方法来处理这个问题吗?
我找到了一个遵循"使用者应该解析"模式的解决方案,它适用于PowerShell和普通.NET应用程序使用者。
想法:
- 生成一个具有内部AssemblyResolve事件处理程序和将处理程序附加到AppDomain.CurrentDomain.AssemblyResolve事件的静态构造函数的类。(到目前为止,这很熟悉。)
-
不是从同一个或另一个类库调用
Resolver
类,而是由使用者直接调用它。当PowerShell是使用者时,请从PowerShell调用Resolver
- 这是因为任何使用者(包括PowerShell)都具有与其加载的程序集相同的
CurrentDomain
。因此,即使事件处理程序附加在某个动态加载的程序集中,当主消费应用程序中的程序集解析失败时,它仍然会被调用
我的Resolver
版本有:
- 静态属性
AssemblyDirectory
,可用于选择性地设置要搜索的目录。如果保留为空,它将使用从Assembly.GetCallingAssembly().Location
中找到的目录 - 一个伪
Register()
方法,除了确保静态构造函数已被调用之外,它实际上什么都不做
PowerShell代码:
# First, load the assembly with the Resolver class. I actually put it in the same
# core library, but it really doesn't matter where it is. It could even be by itself
# in a dynamically compiled assembly built using the Add-Type commandlet
[Reflection.Assembly]::LoadFile("[an assembly that has the Resolver class].dll")
# Call the Register method to force the static constructor to run if it hasn't already
[mycorp.mylib.Resolver]::Register()
$sourcedir = "C:'foo'bar'..some random dir"
# Set the path to search for unresolved assemblies
[mycorp.mylib.Resolver]::AssemblyDirectory = $sourcedir
# Load the dependent assembly
[Reflection.Assembly]::LoadFile("$sourcedir'myparser.dll")
# Create and use an object from the dependent assembly. When the core assembly
# fails at first to resolve, the Resolver's handler is automatically called,
# and everything is peachy
$encoder = New-Object mycorp.mylib.anamespace.aclass
$result = $encoder.DoStuff( $x, $y, $z ... etc )
如果您想知道如何实际处理AssemblyResolve事件,请查看MSDN上的文档:AppDomain.AssemblyResolve event
关于Register-ObjectEvent
:
起初,我尝试直接在PowerShell中构建一个程序集解析程序,并使用Register-ObjectEvent
commandlet进行注册。这是可行的,但有一个问题:PowerShell不支持返回值的事件处理程序。AssemblyResolve处理程序返回Assembly对象。这几乎就是他们的全部目的。
从Windows PowerShell 5.0开始,有两个新接口可用:IModuleAssemblyInitializer和IModuleAssemblyCleanup(包括示例)。
实现这些接口的类在加载或卸载模块时由PowerShell自动加载和调用。
您可以在IModuleAssemblyInitializer
中处理AppDomain.CurrentDomain.AssemblyResolve
事件。
您可以向AppDomain注册事件(请参阅此处),但必须在很多地方进行注册/处理,因为对于每个实例,您无法保证注册已经在任何给定的入口点进行。
在使用类库的应用程序中处理这一问题可能比在库本身中处理要好。