【问题标题】:ResolveEventHandler after Assembly.LoadFromAssembly.LoadFrom 之后的 ResolveEventHandler
【发布时间】:2012-04-16 13:41:20
【问题描述】:

我正在使用 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 事件的情况下它们工作正常。我能够从同一个AppDomain 中的每个位置访问动态加载的Assembly 中的类型。

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

  1. 但现在看来我应该通过 oAssembly instance evertwhere 来使用加载程序集中的任何类型?
  2. 它不会加载程序集,我可以使用简单的Type.GetType(...) 方法在任何地方(相同的 AppDomain)访问它吗?

有人可以补一下我的问题并回答我的问题吗?

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

【问题讨论】:

  • 我们需要更多信息。使用 LoadFile 和 LoadFrom 加载程序集。比较生成的 Assembly Location 和 FullName 属性。此外,尝试在 Type.GetType 调用中添加程序集的全名。您在“MyFolder”和应用程序 EXE 的文件夹中是否有该程序集或其任何依赖项的多个副本?您是否使用多个 AppDomain?多线程?该项目是否托管在 IIS 中?还是其他一些影子复制系统?如果在LoadFrom之前调用GetType,是否成功?大会的位置和全名是什么?
  • 您能否发布一个更完整的测试应用程序来重复问题,以便我们进行测试。是您一直失败的原因,还是取决于加载的路径/程序集,是否存在同一程序集的多个版本等。
  • 您似乎说LoadFrom 并在LoadFrom 返回的完全限定程序集名称上使用Type.GetType(string),如果不设置AssemblyResolve 事件处理程序就无法工作。但是,您在事件处理程序中使用的路径 C:\PSIOBJ\... 和 LoadFrom 行中的路径 C:\MyFolder\... 不匹配。这是剪切和粘贴错误,还是您实际上加载了不同的程序集?

标签: c# .net vb.net assemblies


【解决方案1】:

如果我正确理解了您的问题,那么您正在尝试按照这些思路做一些事情:

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 和AppDomain.RelativeSearchPath 中动态加载的程序集。不幸的是,相对搜索路径必须相对于基本目录。

结果:
这种行为的结果是GetType 确实 只是检查所有加载的程序集。相反,它会以相反的方式工作,它确实:

  1. GetType 使用第一个参数的程序集部分使用Assembly.Load 定位程序集
  2. 如果找到,请反映该程序集中的类型。
  3. 如果未找到,则返回 null 而不尝试其他任何操作(或抛出 FileNotFoundException,这可能会相当混乱)。

您可以自己测试一下:Assembly.Load 在您只提供程序集名称时将不起作用。

解决方案

有几种解决方案。您已经为自己命名的一个并且保留了程序集对象。还有一些,每个都有自己的缺点:

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

  2. 使用通用解析器检查加载的程序集并返回加载的程序集。您无需再次致电LoadFrom。我测试了以下内容,效果非常好,速度非常快:

    // 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. 同时使用 AppDomain.CurrentDomain.AssemblyLoad 和 .AssemblyResolve 事件。第一个用于将每个加载的程序集存储在字典缓存中(按全名),第二个用于通过按名称从中获取值来探测该字典。这实现起来相对简单,并且可能比以前的解决方案执行得稍好。

  4. 使用AppDomain.CurrentDomain.TypeResolve 事件处理程序。我还没有尝试过,所以我不确定它是否适用于您的场景。:这不起作用。 GetType 首先 尝试加载程序集,当失败时,它不会尝试解析类型并且此事件永远不会触发。

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

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

结论

静态方法组Type.GetType(..) 乍一看在加载程序集时表现得相当不直观。一旦您了解了几个上下文背后的想法,请尝试将程序集放置在默认上下文中。如果这不可能,您可以创建一个AssemblyResolve 事件处理程序,这并不难使其普遍适用。

【讨论】:

  • 非常广泛且写得很好的答案。非常感谢。
【解决方案2】:

尝试仅在 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多