在类库中处理AssemblyResolve事件的位置



我需要动态地将程序集引用从一个类库解析到另一个类库。类库是从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:foobar..some random dir"
# Set the path to search for unresolved assemblies
[mycorp.mylib.Resolver]::AssemblyDirectory = $sourcedir
# Load the dependent assembly
[Reflection.Assembly]::LoadFile("$sourcedirmyparser.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注册事件(请参阅此处),但必须在很多地方进行注册/处理,因为对于每个实例,您无法保证注册已经在任何给定的入口点进行。

在使用类库的应用程序中处理这一问题可能比在库本身中处理要好。

最新更新