加载程序集后的 ResolveEventHandler

4

我正在使用Assembly.LoadFrom()加载程序集,因为这些程序集位于应用程序基目录之外的其他路径中。

Dim oAssembly As Assembly = _
Assembly.LoadFrom("C:\\MyFolder\\" + ddlXlate.SelectedItem.ToString() + ".dll")

我从该程序集中成功地调用了一个Type

oXML = CType(oAssembly.CreateInstance(sBaseType + ".XlateContainer"), _
XlateBase.XlateContainer)

然而,当我尝试在另一个方法中使用来自该程序集的 Type 时,出现了问题,就像下面这个方法一样:
oComboBox.DataSource = _
[Enum].GetValues(Type.GetType(sType + "+ItemEnum," + sAssemblyName))

sAssemblyName是我实际使用LoadFrom()加载的程序集。当它说找不到该程序集时,我使用了AssemblyResolve事件来解决我的问题:

订阅AssemblyResolve事件:

AddHandler AppDomain.CurrentDomain.AssemblyResolve, _
AddressOf MyResolveEventHandler

事件处理程序方法:

Private Shared Function MyResolveEventHandler(ByVal sender As Object, _
    ByVal args As ResolveEventArgs) As Assembly
    Return Assembly.LoadFrom("C:\\PSIOBJ\\" + args.Name + ".dll")
End Function

我认为错误可能是因为在使用LoadFrom()加载程序集清单文件时,它找不到定义的依赖程序集,但当我检查args.Name时,发现它正在尝试加载同一程序集,之后就可以正常工作了。所以基本上在添加更改事件之前,无法找到已加载程序集中的类型。
我的旧代码使用了AppDomain.CurrentDomain.Load()Assembly.Load()方法,它们没有使用AssemblyResolve事件也能正常工作。我能够从同一个AppDomain中的任何地方访问动态加载的Assembly中的类型。 LoadFrom()可以在请求的程序集路径内自动查找依赖项,这不应该是问题,因为dll需要的一切都在那里。因此,起初它看起来像是一个AppDomain问题,因为它似乎可以从Load上下文而不是LoadFrom上下文访问程序集,而我现在正在使用LoadFrom上下文。
1. 但现在似乎我应该将oAssembly实例传递到每个地方才能使用已加载程序集中的任何类型? 2. 它不会加载程序集,以便我可以使用简单的Type.GetType(...)方法在任何地方(同一AppDomain)访问它吗?
请问有人能填补缺失的点并回答我的问题吗?
您可以使用C#,实际上我不喜欢VB.NET,但我必须在这里在Office中使用它。

我们需要更多的信息。使用LoadFile和LoadFrom同时加载程序集。比较结果中的Assembly Location和FullName属性。另外,尝试在Type.GetType调用中添加程序集的完整名称。您是否在"MyFolder"和应用程序EXE文件夹中有该程序集或其任何依赖项的多个副本?您是否使用多个AppDomains?多个线程?这个项目托管在IIS中吗?还是其他影子复制系统?如果在LoadFrom之前调用GetType,它是否成功?它的程序集位置和FullName是什么? - Brannon
你能否发布一个更完整的测试应用程序,以便我们可以进行测试并重现问题。你所遇到的问题是否一直存在,还是取决于加载的路径/程序集,是否存在同一程序集的多个版本等。 - NSGaga-mostly-inactive
看起来你说 LoadFrom 和使用 Type.GetType(string)LoadFrom 返回的完全限定程序集名称上,如果没有设置 AssemblyResolve 事件处理程序是行不通的。然而,你在事件处理程序中使用的路径 C:\PSIOBJ\...LoadFrom 行中的路径 C:\MyFolder\... 不匹配。这是剪切和粘贴错误吗?还是你实际上加载了不同的程序集? - Abel
2个回答

10

如果我理解您的问题正确,您正在尝试做类似以下的事情:

var asm = Assembly.LoadFrom(@"D:\Projects\_Libraries\FluentNH 1.1\Castle.Core.dll");
var obj = asm.CreateInstance("Castle.Core.GraphNode");
var type = Type.GetType(obj.GetType().AssemblyQualifiedName, true);  // fails

您所遇到的问题是,无论您使用哪种形式的程序集加载,当库不在与可执行文件相同的路径中时,变量type始终为null

原因

您遇到的问题是.NET中程序集不同的加载上下文。一般有三种,实际上是四种加载上下文:

  • 默认加载上下文。这用于GAC中的所有程序集,当前执行的程序集,当前路径中的程序集(请参阅BaseDirectory),以及PrivatePath(请参阅RelativeSearchPath)中的程序集。Assembly.Load(string,..)使用此上下文。
  • 从磁盘加载上下文。这是用于任何不在探测路径中的磁盘上程序集的上下文,通常使用Assembly.LoadFrom进行加载。
  • 仅反射上下文。在此上下文中的类型不能被执行。
  • 无上下文。当您使用Assembly.Load(byte\[\],..)Assembly.LoadFile方法之一加载程序集,或者加载未保存到磁盘的动态程序集时,将使用此上下文。
Types在一个上下文中加载,与另一个上下文不兼容(您甚至不能将相等的类型从一个上下文转换为另一个上下文!)。专门在一个上下文中操作的方法无法访问另一个上下文。Type.GetType(string)只能在默认上下文中加载类型,除非您稍微帮助该方法。
这正是您遇到的问题。当程序集dll在应用程序路径中时,一切都正常。一旦您移动它,事情开始崩溃。
更具体地说: 当您调用Type.GetType(string)时,它将查询路径中所有静态引用的程序集以及当前路径(AppDomain.BaseDirectory)中的动态加载的程序集、GAC和AppDomain.RelativeSearchPath中的程序集。不幸的是,相对搜索路径必须相对于基本目录。
结果: 这种行为的结果是GetType并不仅仅检查所有已加载的程序集。相反,它是反过来的,它执行以下操作:
  1. GetType使用第一个参数的程序集部分来定位程序集,使用Assembly.Load加载程序集。
  2. 如果找到,从该程序集中反射类型。
  3. 如果没有找到,则返回null而不尝试其他任何操作(或抛出FileNotFoundException,这可能会令人困惑)。

您可以自行测试此功能:Assembly.Load在仅提供程序集名称时无法工作。

解决方案

有几种解决方案。其中一种是保留程序集对象。还有一些更多,每个都有自己的缺点:

  1. Use GetType() on the instantiated object itself, instead of the static method Type.GetType(string). This has the advantage that you don't need the assembly qualified name of the type, which can be hard to get (in your example, you don't say how you set sAssemblyName, but isn't that also something you need floating around?).

  2. Use a generic resolver that checks the loaded assemblies and returns the loaded assembly. You don't need to call LoadFrom again. I tested the following and that works splendidly and quite fast:

    // works for any loaded assembly, regardless of the path
    private static Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args)
    {
        // you may not want to use First() here, consider FirstOrDefault() as well
        var asm = (from a in AppDomain.CurrentDomain.GetAssemblies()
                  where a.GetName().FullName == args.Name
                  select a).First();
        return asm;
    }
    
    // set it as follows somewhere in the beginning of your program:
    AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve;
    
  3. Use the AppDomain.CurrentDomain.AssemblyLoad and .AssemblyResolve events together. The first you use to memorize each loaded assembly in a dictionary cache (by full name), the second you use to probe that dictionary by getting the value from it by name. This is relatively trivial to implement and might perform slightly better than the previous solution.

  4. Use the AppDomain.CurrentDomain.TypeResolve event handler. I haven't tried this, so I'm not certain it'll work in your scenario.: this doesn't work. GetType first tries to load the assembly, when that fails, it doesn't try to resolve the type and this event never fires.

  5. Add the libraries you want to resolve to the GAC or to any (relative) path of your application. This is by far the easiest solution.

  6. Add the paths to app.config. This only works for strongly typed assemblies, in which case you could just as easily load them in the GAC. Not-strongly-typed assemblies must still be in a relative path to the current application.

结论

Type.GetType(..) 静态方法在处理已加载的程序集时可能会表现出令人困惑的行为。一旦理解了不同上下文背后的概念,尝试将程序集放置在默认上下文中。如果无法实现这一点,您可以创建一个 AssemblyResolve 事件处理程序,这并不难以泛化应用。


非常全面且写得很好的回答。非常感谢。 - Tarik

0
尝试只返回汇编代码,其中参数 args.name 是正确的...
 Private Shared Function MyResolveEventHandler(ByVal sender As Object, _
 ByVal args As ResolveEventArgs) As Assembly
 //Ex:     if (args.name.contains("XLATEBASE")) {Return Assembly.LoadFrom("C:\\PSIOBJ\\" + args.Name + ".dll")}
 End Function

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接