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

时间:2012-02-01 16:21:22

标签: c# dll assemblies

我需要动态解决从一个类库到另一个类库的程序集引用。类库是从PowerShell脚本加载的,因此在可执行文件中查找依赖程序集的默认.NET行为会直接失败,因为可执行文件是PowerShell本身。如何使这些依赖的汇编引用解析/正常工作?

更详细

我有两个实用程序库:一个核心库和另一个执行一些非常具体的解析任务的实用程序库。我想在PowerShell脚本中动态加载它们,而不在GAC中安装它们。第二个库取决于第一个库。在VS解决方案中,解析库具有对核心库的项目引用,Copy Local = true

使用后,我可以在解析库输出bin(/ Debug | / Release)文件夹中加载和使用这两个库(PowerShell here):

[Reflection.Assembly]::LoadFile("C:\...thefile.dll")

但是,每当在解析(依赖)库中调用一个从核心库调用某些东西的方法时,它就无法解析核心程序集。这令人沮丧...因为文件在同一个文件夹中。一个或两个是否具有强名称密钥没有区别。

我现在的解决方法是处理AssemblyResolve事件。棘手的问题是找出将其放在类库中的位置,因为没有单一的入口点总是在可执行Main()方法之前的其他任何东西之前执行(参见Is there an equivalent of Application_Start for a class library in c#)。

现在我创建了一个带有静态构造函数的静态Resolver类,该构造函数附加了AssemblyResolve的处理程序,然后在每个解析类中都有一个静态构造函数,它引用静态解析器class,强制解析器类的静态构造函数执行。结果是AssemblyResolve事件只被连接一次,并使用通用的中央代码进行处理。所以它有效。但我讨厌必须在我的所有解析类中添加一个时髦的静态构造函数。

有没有更好的方法来解决这个问题?

3 个答案:

答案 0 :(得分:2)

我找到了一个遵循“消费者应该解决”模式的解决方案,它适用于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命令行开关注册它。这可以工作,但是对于一个问题:PowerShell不支持返回值的事件处理程序。 AssemblyResolve处理程序返回一个Assembly对象。这几乎是他们的全部目的。

答案 1 :(得分:0)

您可以使用AppDomain注册活动(请参阅here),但必须在许多地方注册/处理,因为对于每个实例,您无法保证在任何给定的情况下都已进行注册切入点。

在使用类库而不是库本身的应用程序中处理此问题可能更好。

答案 2 :(得分:0)

从 Windows PowerShell 5.0 开始,有两个新接口可用:IModuleAssemblyInitializer and IModuleAssemblyCleanup(包括示例)。

实现这些接口的类在加载或卸载模块时由 PowerShell 自动加载和调用。

您可以在 AppDomain.CurrentDomain.AssemblyResolve 中处理 IModuleAssemblyInitializer 事件。