VSTO:操作COM对象(“一个点好,两个点不好”)

来自Excel VBA背景,我会经常写代码,如:

Range("myRange").Offset(0, 1).Resize(1, ccData).EntireColumn.Delete 

我现在正在转移到VSTO,并且一直在阅读关于RCW计数器等,以及需要显式释放COM对象。 基本的build议似乎是:不要将Excel对象的引用链接在一起(如上所述) – 因此“一个好点,两点差”。 我的问题是,我是否正确,上面的代码是不是在VSTO去的路? 如果是这样,这是否意味着我需要明确声明上面的链(偏移量,resize和整个列)暗示的3个范围?

或者甚至如此:

 rng.Columns.Count 

rng是一个声明的范围? 我应该为rng.Columns分配一个名称以获得范围中的列数?

这种“两点统治”的愚蠢背后有着非常有害的货物邪教,它完全没有让C#程序员从第四版开始就没有麻烦了。而且让Office程序按需退出的简单方法 ,更让人头痛不已。

但这完全不是问题,货物崇拜只适用于使用自动化激活Office程序的进程外程序。 你的代码实际上 Office程序中运行,你当然不在乎程序什么时候终止。 因为这也会终止你的代码。

只要按照编写常规C#代码的方式编写代码,GC就不需要任何帮助。

我是否正确,上面的代码是不是在VSTO去的路?

是的,你在正确的道路上。 您需要声明不同的对象以便稍后释放它们。 使用System.Runtime.InteropServices.Marshal.ReleaseComObject完成使用时释放Office对象。 然后在Visual Basic中将variables设置为Nothing(C#中为null)以释放对该对象的引用。

您可以在“ 系统地发布对象”文章中阅读更多内容。 它与Outlook有关,但同样的原则可以应用于所有的Office应用程序。

顺便说一句:它不依赖于VSTO。 它来自COM世界…

由于经常发生小的肯定,双点规则需要进一步解释。 你可以用它作为助记符。

原因是你在.NET中获得的每个新的RCW都将持有对各自COM对象的引用。 所以,如果你有(假设obj是一个RCW,这是你第一次得到其他对象):

 obj.Property[0].MethodThatReturnsAnotherObject() // 1 2 3 

你会得到3个额外的RCWs。 正如你所看到的,只有一个额外的点。 尽pipe属性可能是获取其他COM对象最常用的方法,但这不是唯一的方法。

通常,除非使用Marshal.ReleaseComObject ,否则每个RCW只会在垃圾收集时释放底层的COM对象。 只有使用这种方法,如果你完全确定你是唯一使用你正在发布的RCW。


要完全清楚这个问题:

只有使用ReleaseComObjectFinalReleaseComObject如果你真的明确地必须完全确定你的代码是唯一指向RCW的代码。


 <type> propObj; try { propObj = obj.Property; <type> propArrayObj; try { propArrayObj = propObj[0]; <type> propArrayObjReturn; try { propArrayObjReturn = propArrayObj.MethodThatReturnsAnotherObject(); } finally { if (propArrayObjReturn != null) Marshal.ReleaseComObject(propArrayObjReturn); } } finally { if (propArrayObj != null) Marshal.ReleaseComObject(propArrayObj); } } finally { if (propObj != null) Marshal.ReleaseComObject(propObj); } 

这很乏味,一个包装可能会帮助这里:

 using System; using System.Runtime.InteropServices; using System.Threading; public class ComPtr<T> : IDisposable where T : class { public ComPtr(T comObj) { if (comObj == null) throw new ArgumentNullException("comObj"); if (!typeof(T).IsInterface) { throw new ArgumentException("COM type must be an interface.", "T"); } // TODO: check interface attributes: ComImport or ComVisible, and Guid this.comObj = comObj; } private T comObj; public T ComObj { get { // It's not best practice to throw exceptions in getters // But the alternative might lead to a latent NullReferenceException if (comObj == null) { throw new ObjectDisposedException("ComObj"); } return comObj; } } ~ComPtr() { Dispose(false); } // IDisposable public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected void Dispose(bool disposing) { #if !RELEASECOMPTR // Option 1: Safe. It might force the GC too often. // You can probably use a global limiter, eg don't force GC // for less than 5 seconds apart. if (Interlocked.Exchange(ref comObj, null) != null) { // Note: GC all generations GC.Collect(); // WARNING: Wait for ALL pending finalizers // COM objects in other STA threads will require those threads // to process messages in a timely manner. // However, this is the only way to be sure GCed RCWs // actually invoked the COM object's Release. GC.WaitForPendingFinalizers(); } #else // Option 2: Dangerous! You must be sure you have no other // reference to the RCW (Runtime Callable Wrapper). T currentComObj = Interlocked.Exchange(ref comObj, null); if (currentComObj != null) { // Note: This might (and usually does) invalidate the RCW Marshal.ReleaseComObject(currentComObj); // WARNING: This WILL invalidate the RCW, no matter how many // times the object reentered the managed world. // However, this is the only way to be sure the RCW's // COM object is not referenced by our .NET instance. //Marshal.FinalReleaseComObject(currentComObj); } #endif } } 

这将使前面的例子更友好:

 using (var prop = new ComObj<type>(obj.Property)) { using (var propArray = new ComObj<type>(prop.ComObj[0])) { using (var propArrayReturn = new ComPtr<type>(propArray.ComObj.MethodThatReturnsAnotherObject())) { } } } 

为了避免ComObj属性,你可以实现一个代理,但是我将把它作为一个练习。 具体来说,做一个高效的代理生成,而不是reflection转发。