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

Ðаn*_*Ðаn 16 .net c# com interop com-interop

我在.NET(C#)中使用了广泛的现有COM API(可能是Outlook,但它不是).我通过在Visual Studio中添加"COM引用"来完成此操作,因此所有"魔法"都是在幕后完成的(即,我不必手动运行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);
       }
   }
}
Run Code Online (Sandbox Code Playgroud)

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

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

在大多数情况下,这可能是意外的.修复此问题可能看起来像

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;             
       }
   }
}
Run Code Online (Sandbox Code Playgroud)

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

看起来解决方案的一部分是以某种方式使用WeakReference.还有关于使用IDisposable的建议,但是对于每个对象都必须处理Dispose()似乎对.NET不友好.然后是关于何时/ 应该调用ReleaseComObject的各种讨论.也有代码在上http://codeproject.com使用后期绑定,但我很高兴与版本相关的API.

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

我想尝试的一件事是创建一个ATL项目,使用/ clr标志进行编译并使用C++的编译器COM支持(#import创建的Product :: ComObjectPtr)而不是.NET RCW.当然,我通常宁愿用C#编写代码而不是C++/CLI ...

Kar*_*rlW 12

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

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

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


Bra*_*son 6

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

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

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

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