Assembly.LoadFrom之后的ResolveEventHandler

时间:2012-04-02 21:34:13

标签: c# .net vb.net assemblies

我使用Assembly.LoadFrom()加载程序集,因为程序集位于与Application Base目录不同的路径中。

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事件的情况下工作正常。我能够从同一Assembly内的每个位置到达动态加载AppDomain的类型。

LoadFrom()可以在同一个请求的程序集路径中自动找到依赖项,这可能不是问题,因为dll需要的所有内容都存在。所以起初它看起来像AppDomain问题,因为看起来它似乎可以从Load上下文而不是LoadFrom上下文到达程序集,我现在正在使用LoadFrom上下文。

  1. 但现在看来我应该通过oAssembly实例evertwhere来使用加载程序集中的任何类型?
  2. 它是否使用简单的Type.GetType(...)方法加载我可以到达任何地方(相同的AppDomain)的程序集?
  3. 有人可以填写错过的分数并回答我的问题吗?

    你可以使用C#,实际上我不喜欢VB.NET,但我必须在Office中使用它。

2 个答案:

答案 0 :(得分:9)

如果我正确地理解了你的问题,那么你正试图按照以下方式做点什么:

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

原因

您遇到的问题是different loading contexts for assemblies in .NET。通常有三种,实际上有四种类型的加载上下文,简而言之:

  • 默认加载上下文。这用于GAC中的所有程序集,当前执行的程序集,当前路径中的程序集(请参阅BaseDirectory)和PrivatePath中的程序集(请参阅RelativeSearchPath)。 Assembly.Load(string,..)使用此上下文。
  • 来自上下文的加载。这是用于探测路径中而非上的任何程序集的上下文,通常加载Assembly.LoadFrom
  • 仅反射上下文。此上下文中的类型无法执行。
  • 无上下文背景。使用任何Assembly.Load(byte\[\],..)Assembly.LoadFile方法加载程序集时,或者加载未保存到磁盘的动态程序集时,将使用此上下文。

在一个上下文中加载的类型与另一个上下文不兼容(您甚至无法将相同的类型从一个上下文转换为另一个上下文!)。专门针对一个上下文操作的方法无法访问另一个上下文。 Type.GetType(string) 只能在默认上下文中加载类型,除非您稍微帮助该方法。

这正是您遇到的。当程序集dll在你的应用程序的路径中时,一切正常。一旦你移动它,事情开始崩溃。

更具体地说:
当您致电Type.GetType(string)时,它会查询路径中所有静态引用的程序集,并在当前路径(AppDomain.BaseDirectory),GAC和{{1}中查询动态加载的程序集和。不幸的是,相对搜索路径必须相对于基本目录。

<强>结果:
此行为的结果是GetType 只检查所有已加载的程序集。相反,它反过来工作,它确实:

  1. GetType使用第一个参数的程序集部分来使用AppDomain.RelativeSearchPath
  2. 找到程序集
  3. 如果找到,请反映该程序集中的类型。
  4. 如果找不到,则返回null而不尝试任何其他操作(或抛出Assembly.Load,这可能相当混乱)。
  5. 您可以自己测试一下:当您只提供程序集名称时,FileNotFoundException将无效。

    解决方案

    有几种解决方案。一个你已经为自己命名并保持装配对象的一个​​。还有一些,每个都有自己的缺点:

    1. 对实例化对象本身使用Assembly.Load,而不是静态方法GetType()。这样做的好处是,您不需要类型的程序集限定名称,这可能很难获得(在您的示例中,您没有说明如何设置Type.GetType(string),但这也不是你需要漂浮吗?)。

    2. 使用通用解析程序检查已加载的程序集并返回已加载的程序集。您无需再次致电sAssemblyName。我对以下内容进行了测试,结果非常出色:

      LoadFrom
    3. 同时使用AppDomain.CurrentDomain.AssemblyLoad和.AssemblyResolve事件。第一个用于记忆字典缓存中的每个已加载的程序集(按全名),第二个用于通过按名称从中获取值来探测该字典。这实现起来相对简单,并且可能比以前的解决方案稍微好一些。

    4. 使用// 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; 事件处理程序。我没试过这个,所以我不确定它会在你的场景中起作用。:这不起作用。 GetType first 尝试加载程序集,当失败时,它不会尝试解析类型,并且此事件永远不会触发。

    5. 将要解析的库添加到GAC或应用程序的任何(相对)路径。这是迄今为止最简单的解决方案。

    6. 将路径添加到app.config。这仅适用于强类型程序集,在这种情况下,您可以轻松地将它们加载到GAC中。非强类型程序集仍必须位于当前应用程序的相对路径中。

    7. 结论

      静态方法组AppDomain.CurrentDomain.TypeResolve在第一次看到加载的程序集时表现得相当不直观。一旦理解了几个上下文背后的想法,尝试将程序集放在默认上下文中。如果无法做到这一点,您可以创建一个Type.GetType(..)事件处理程序,这对于一般情况下并不困难。

答案 1 :(得分: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