1

为 COM 互操作注册的 .NET 程序集与一些应在程序集中加载类型的反射代码相结合会导致一些奇怪的行为。我已经分析了调试器中发生了什么,并且我已经搜索了网络以找到解决方案。我找到了很多有帮助的文章,但没有什么能让我完全解决问题。

问题概述

A 有一个不是 .NET 的 exe 文件(在我的情况下是 VB6 应用程序)。它位于文件夹 A 中。
我在文件夹 B 中有一些 .NET dll。其中一个是 COM dll。
exe 文件实例化 COM .NET 程序集的 .NET 对象的 COM 实例。然后 AppDomain 的主路径是文件夹 A,但我希望它是文件夹 B。因为它是文件夹 A,所以我的 .NET 代码中的一些反射类型加载失败。

以下是详细信息:

我有一个 VB6 应用程序。exe 文件位于文件夹 A 中。在其中我有一个 VB6 语句

Set DotNetE2 = CreateObject("MyDotNet.E2")

这将创建一个为 COM 互操作注册的 .NET 类的实例。.NET 类的标头如下所示:

namespace MyDotNet.E2.COM
{
   [ComVisible(true)]
   [Guid("776FF4EA-2F40-4E61-8EF3-08250CB3712B")]
   [ProgId("MyDotNet.E2")]
   [ClassInterface(ClassInterfaceType.AutoDual)]
   public class E2
   {

我的 .NET 程序集“MyDotNet.E2.COM.dll”位于文件夹 B 中。该程序集引用了位于同一文件夹中的其他两个名为 E3 和 E4 的 .NET 程序集。E3 没有对 E4 的引用。我是能够按预期执行这些程序集中的代码,所以引用是好的。到目前为止一切都很好。现在,我在 E3 中有一些代码试图对 E4 中的类型进行一些反射。这失败了。

这是代码:

string dotnetPath = Path.GetDirectoryName(
                 Assembly.GetExecutingAssembly().Location);
string mainDir = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
string otherDirs = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;

Assembly assembly = Assembly.LoadFrom(Path.Combine(dotnetPath, "E4.dll"));
Type mytype = assembly.GetType("MyDotnet.E4.MyForm");

观察

dotnetPath 与 mainDir 不同。
我的类型为空。预期结果是一个类型实例。
如果我将 exe 文件连同 .NET 程序集一起移动到文件夹 B,它就可以工作。那么 dotnetPath 和 mainDir 是一样的。
如果我在 E2 而不是 E4 中执行反射代码,它可以工作,即使 dotnetPath != mainDir.
但在我所概述的场景中,它不起作用。

通过在配置文件中指定这些文件夹,我发现了一些关于将其他文件夹添加到 PrivateBinPath 中的 AppDomain 的提示。但我在这方面没有成功。我试图向我的 COM .NET 文件和我的 VB6 exe 文件添加一个配置文件,但我的 PrivateBinPath 属性没有发生任何事情。这是我尝试添加的配置文件:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
     <probing privatePath="..\..\FolderB"/>
    </assemblyBinding>
  </runtime>
</configuration>

不要让我重组我的程序集和类型。该项目相当复杂,这是最好的架构。

4

1 回答 1

1

我设法自己解决了这个问题。关键是在系统无法解析程序集时触发的 AssemblyResolve 事件。这里解释:http: //msdn.microsoft.com/library/system.appdomain.assemblyresolve

我必须使用此事件似乎是 .NET 框架中的一个错误,但结果证明这种解决方法相当不错。

string dotnetPath = Path.GetDirectoryName(
                                   Assembly.GetExecutingAssembly().Location);
string mainDir = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;

if (!mainDir.Equals(dotnetPath, StringComparison.CurrentCultureIgnoreCase))
{
   // This will happen if .NET process is fired 
   // from a COM call from another folder.
   // Solution: an event is fired if assembly-resolving fails.
   AppDomain.CurrentDomain.AssemblyResolve += 
                         new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}

事件处理程序相当简单:

Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
   foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
   {
      if (assembly.FullName == args.Name) return assembly;
   }
   return null;
}
于 2012-04-24T11:34:51.123 回答