如何在本机.NET类中包装COM对象?

时间:2010-02-13 20:21:57

标签: c# .net com interop com-interop

我在.NET(C#)中使用了广泛的现有COM API(可能是Outlook,但它不是)。我通过在Visual Studio中添加“COM Reference”来完成此操作,因此所有“魔法”都在幕后完成(即,我不必手动运行 tlbimp )。

虽然现在可以从.NET“轻松”使用COM API,但它不是非常友好的.NET。例如,没有泛型,事件是奇怪的,奇怪的是像 IPicture 等等。所以,我想创建一个使用现有COM API实现的本机.NET API

简单的第一遍可能是

namespace Company.Product {
   class ComObject {
       public readonly global::Product.ComObject Handle; // the "native" COM object
       public ComObject(global::Product.ComObject handle) {
          if (handle == null) throw new ArgumentNullException("handle");
          Handle = handle;
       }

       // EDIT: suggestions from nobugz
       public override int GetHashCode() {
          return Handle.GetHashCode();
       }
       public override bool Equals(object obj) {
          return Handle.Equals(obj);
       }
   }
}

这种方法的一个直接问题是,您可以轻松地为同一个底层“本机COM”对象结束多个 ComObject 实例。例如,在进行枚举时:

IEnumerable<Company.Product.Item> Items {
   get {
      foreach (global::Item item in Handle.Items)
         yield return new Company.Product.Item(item);
   }
}

在大多数情况下,这可能会出乎意料。修复此问题可能看起来像

namespace Company.Product {
   class ComObject {
       public readonly global::Product.ComObject Handle; // the "native" COM object
       static Dictionary<global::Product.ComObject, ComObject> m_handleMap = new Dictionary<global::Product.ComObject, ComObject>();
       private ComObject(global::Product.ComObject handle) {
          Handle = handle;
          handleMap[Handle] = this;
       }
       public ComObject Create(global::Product.ComObject handle) {
          if (handle == null) throw new ArgumentNullException("handle");

          ComObject retval;
          if (!handleMap.TryGetValue(handle, out retval))
              retval = new ComObject(handle);
          return retval;             
       }
   }
}

看起来更好。枚举器更改为调用Company.Product.Item.Create(item)。 但现在问题是 Dictionary&lt;&gt; 会使两个对象都“活着”,所以它们永远不会被垃圾收集;这可能对COM对象不利。现在事情开始变得混乱......

看来part of the solution以某种方式使用 WeakReference 。关于使用 IDisposable 还有suggestions,但是对于每个对象都要处理 Dispose()似乎对.NET不友好。然后,应该调用when / if ReleaseComObject 的各种discussions。使用后期绑定的code上还有http://codeproject.com,但我对依赖于版本的API感到满意。

所以,在这一点上,我不确定什么是最好的方法。我希望我的原生.NET API尽可能像“.NET一样”(甚至可能用.NET 4.0嵌入Interop程序集),而不必使用像“两点”规则那样的启发式算法。

我想尝试的一件事是创建一个ATL项目,使用 / clr 标志进行编译并使用C ++的编译器COM支持( Product :: ComObjectPtr 创建者 #import )而不是.NET RCW。当然,我一般都是rather code in C#而不是C ++ / CLI ...

4 个答案:

答案 0 :(得分:11)

你自己不是在处理COM对象。您已经在处理在向项目添加对COM二进制文件的引用时创建的外观。 (.NET)将为您生成一个Facade,因此简化了使用COM对象简单地使用常规.NET类的任务。如果您不喜欢为您生成的界面,则应该为现有外观创建一个外观。您不必担心COM复杂性,因为已经为您完成了(您可能需要担心一些事情,但我认为它们很少而且很远)。只需将该类用作常规的.net类,因为它就是它的本质,并在出现任何问题时处理它们。

编辑:您可能遇到的一个问题是不确定的COM对象破坏。在幕后发生的引用计数依赖于垃圾收集,因此您无法确定何时销毁对象。根据您的应用程序,您可能需要对COM对象进行更确定的破坏。为此,您将使用 Marshal.ReleaseComObject 如果就是这种情况,那么您应该了解this问题。

抱歉,我会发布更多链接,但显然我不能在没有获得10点声望的情况下发布超过1个。

答案 1 :(得分:6)

我将COM对象引入.NET时发现的最大问题是垃圾收集器在不同的线程上运行,并且COM对象的最终版本通常(总是?)从该线程调用。

Microsoft故意破坏了这里的COM线程模型规则,该规则规定,对于单元线程对象,必须从同一个线程调用所有方法。

对于某些COM库而言,这不是什么大问题,但对于其他人而言,这是一个很大的问题 - 特别是对于需要在析构函数中释放资源的库。

要注意的事情......

答案 2 :(得分:2)

你使它变得不必要的困难。它不是“句柄”,不需要引用计数。 __ComObject是一个常规的.NET类,受普通垃圾收集规则的约束。

答案 3 :(得分:0)

听起来您正在寻找围绕复杂COM API创建更简单的.NET API。正如nobugz所说,您的ComObject类是真实的本机.NET对象,它们内部包含对实际com对象的非托管引用。你不需要做任何有趣的事情来管理它们......只需使用它们就像它们是普通的.NET对象一样。

现在,关于向.NET消费者呈现“更漂亮的面孔”。现有一种设计模式,称为Facade。我将假设您只需要这些COM对象提供的部分功能。如果是这种情况,则在com interop对象周围创建一个Facade图层。该层应包含必要的类,方法和支持类型,这些类,方法和支持类型为所有.NET客户端提供必要的API,其API比com对象本身更友好。外观还负责简化奇怪的事情,例如转换com对象为普通.NET事件,数据封送,简化com对象创建,设置和拆卸等事件所做的事情。

虽然一般来说应该避免抽象,因为它们往往会增加工作量和复杂性。但有时它们是必要的,在某些情况下可以大大简化。如果一个更简单的API可以提高需要使用非常复杂的com对象系统提供的某些功能的其他团队成员的生产力,那么抽象可以提供有形的价值。