我需要动态解决从一个类库到另一个类库的程序集引用。类库是从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事件只被连接一次,并使用通用的中央代码进行处理。所以它有效。但我讨厌必须在我的所有解析类中添加一个时髦的静态构造函数。
有没有更好的方法来解决这个问题?
答案 0 :(得分:2)
我找到了一个遵循“消费者应该解决”模式的解决方案,它适用于PowerShell和普通的.NET应用程序使用者。
这个想法:
Resolver
类,而是直接由消费者调用它。当PowerShell是使用者时,从PowerShell调用Resolver
。 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
事件。