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

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

虽然现在可以从.NET“轻松”使用COM API,但它不是非常友好的.NET。 例如,没有generics,事件很奇怪,像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 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 m_handleMap = new Dictionary(); 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 会使两个对象“活着”,所以它们永远不会被垃圾收集; 这可能对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 …

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

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

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

需要注意的事情……

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

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

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

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

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

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

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