VSTO:操纵COM对象(“一点好,两点坏”)

时间:2015-03-15 23:57:47

标签: c# excel vba com vsto

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

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

我现在正在转向VSTO,并且一直在阅读RCW计数器等,以及明确释放COM对象的需要。基本建议似乎是:不要将Excel对象的引用链接在一起(如上所述) - 因此"一个点好,两个点坏"。 我的问题是,我是否正确上述代码不是VSTO的方式?如果是这样,这是否意味着我需要明确声明上述链中隐含的3个范围(Offset,Resize& EntireColumn)?

甚至如此:

rng.Columns.Count

其中rng是声明的范围?我应该为rng.Columns指定一个名称,以获得范围内的列数吗?

3 个答案:

答案 0 :(得分:6)

“双点规则”愚蠢背后存在着非常不利的货物崇拜,它完全无法让C#程序员从第4版开始就摆脱麻烦。而且,simple way使Office程序退出的过程更加痛苦。需求。

但这不是根本不存在的问题,货物崇拜仅适用于使用自动化激活Office程序的进程外程序。你的代码实际上是在里面运行Office程序,你当然不关心程序什么时候终止。因为这也会终止你的代码。

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

答案 1 :(得分:0)

  

我是否认为上述代码不适合VSTO?

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

您可以在Systematically Releasing Objects文章中详细了解相关内容。它与Outlook有关,但相同的原则可以应用于所有Office应用程序。

BTW:它不依赖于VSTO。它来自COM世界......

答案 2 :(得分:0)

正如小肯定经常发生的那样,双点规则需要进一步解释。您可以将其用作助记符。

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

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

你还有3个RCW。如您所见,只有一个额外的点。虽然属性可能是获取其他COM对象的最常用方法,但它并不是唯一的方法。

通常,每个RCW只会在收集垃圾时释放底层COM对象,除非您使用Marshal.ReleaseComObject。只有在完全确定时才使用此方法,您是唯一使用您正在发布的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, e.g. 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属性,您可以实现代理,但我会将其作为练习。具体而言,进行有效的代理生成,而不是通过反射进行转发。