如何在Excel 2007 VBA模块中调用System.Runtime.InteropServices.Marshal.ReleaseComObject

时间:2012-11-27 00:00:54

标签: com excel-vba com-interop excel-interop vba

我想在我的VBA代码中实例化的一些第三方COM对象上调用System.Runtime.InteropServices.Marshal.ReleaseComObject(obj),来自我的工作簿关闭事件,如下所示:

    Public Sub disconnect(obj As Variant)
        Dim refs As Long
        refs = 0

        If Not obj Is Nothing Then
            Do
                refs = System.Runtime.InteropServices.Marshal.ReleaseComObject(obj)
            Loop While (refs > 0)
        End If
    End Sub

但是,我使用上面的代码突出显示compile error: invalid qualifier System。搜索似乎没有返回从VBA宏调用System.Runtime方法的任何VBA代码 - 我只能找到VB.Net自动化Excel。我不确定它是否可能。

我正在尝试通过确保在Excel退出之前妥善处理这些第三方COM对象来解决此问题:Excel 2007 Zombie Process not COM automation but w/ references to 3rd party com objects

2 个答案:

答案 0 :(得分:1)

我没有使用过VBA,但我认为它不是.NET语言,所以很自然地你不能在VBA中使用.NET Framework类,如System.Runtime.InteropServices.Marshal。

当您希望Excel退出时,您确定要删除对COM对象的所有引用吗?确保您为COM对象的每个引用放置如下所示的行:

obj = Nothing ' Where "obj" is a reference to the COM object

如果这不能解决问题,那么问题也可能是循环引用。 COM对象是否将refenrece存储到您的VBA对象中,而VBA对象又保存对COM对象的引用?如果是这样,将创建循环引用,并且永远不会释放对象。我正在寻找别的东西,发现了一个与你非常相似的帖子:

Forcing Garbage Collection

如果这是问题,你真的需要一种方法来手动释放COM对象(并且几次直到引用计数为零)类似于Marshal.ReleaseComObject,但我不知道你怎么能这样做VBA。

其他几个相似的主题:

How to release inprocess COM Server object from Excel VBA Excel process remains open after interop; traditional method not working

答案 1 :(得分:0)

即使在仔细检查和删除循环引用后,我也无法使用推荐的仅使用VBA的方法来解决僵尸问题。

其他搜索方式可以调用ReleaseCom方法using code that wraps System.Runtime.InteropServices.Marshal.FinalReleaseComObject to create a COM visible dll that can be called from VBA

使用教程"how to create a com object using vs 2008 and consume it from vb6.0 client"和新安装的VS2010 Express副本,我能够创建一个可从VBA调用的COM可见dll。

这是稍微修改过的包装器以及如何构建dll:

    using System;
    using System.Collections.Generic;
    using System.Runtime.InteropServices;
    using System.EnterpriseServices;


    namespace ComDisposerLib
    {
        [ClassInterface(ClassInterfaceType.None)]
        [ComponentAccessControl(false)]
        public class ComDisposer : System.EnterpriseServices.ServicedComponent, IDisposable, ComDisposerLib.IComDispose
        {
            private List<Object> _comObjs;

            public ComDisposer()
            {
                _comObjs = new List<Object>();
            }

            ~ComDisposer()
            {
                Dispose(false);
            }

            public Object Add(Object o)
            {
                if (o != null && o.GetType().IsCOMObject)
                    _comObjs.Add(o);
                return o;
            }

            public void Clear()
            {
                Dispose(true);
            }

            protected override void Dispose(bool disposing)
            {
                if (disposing)
                {
                    for (int i = _comObjs.Count - 1; i >= 0; --i)
                        Marshal.FinalReleaseComObject(_comObjs[i]);
                    _comObjs.Clear();
                }
            }

            void IDisposable.Dispose()
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }
        }
    }

和界面:

    using System;

    namespace ComDisposerLib
    {
        public interface IComDispose
        {
            Object Add(Object o);
            void Clear();
            void Dispose();
        }
    }

要构建,创建一个新的类库项目,添加对System.Runtime.InteropServicesSystem.EnterpriseServices的引用,启用“签署程序集”(在项目/属性/签名下的菜单中)并选择或创建一个密钥文件。添加类和接口代码文件。在AssemblyInfo.cs文件中(位于属性下)添加

using System.Runtime.InteropServices;
using System.EnterpriseServices;

[assembly: ComVisible(true)]
[assembly: ApplicationName("ComDisposer")]
[assembly: ApplicationActivation(ActivationOption.Library)]

并建立。如果一切顺利,您可以按如下方式注册dll:

regsvcs "C:\Documents and Settings\username\My Documents\Visual Studio 2010\Projects\ComDispose\ComDispose\obj\Release\ComDisposer.dll"

在VBA中,在添加对新COM库的引用后,请按如下方式使用它:

Sub disposeGlobalComObjects()
' global scope objects used only to simplify example 

    Dim cd As ComDisposer
    Set cd = New ComDisposer

    If Not SomeGlobalComObject Is Nothing Then
        cd.Add SomeGlobalComObject
        Set SomeGlobalComObject = Nothing
    End If
    If Not AnotherGlobalComObject Is Nothing Then
        cd.Add AnotherGlobalComObject
        Set AnotherGlobalComObject = Nothing
    End If
    cd.Dispose
End Sub

早期测试表明它正在运行,即Excel干净利落地关闭,不再创建僵尸进程。

有趣的是,我只是遇到了这个method for using your dll from VBA without having to register it first,如果您无法访问客户端计算机上的注册表,这可能会很有用。