【问题标题】:How can I call System.Runtime.InteropServices.Marshal.ReleaseComObject from within an Excel 2007 VBA module如何从 Excel 2007 VBA 模块中调用 System.Runtime.InteropServices.Marshal.ReleaseComObject
【发布时间】:2012-11-14 13:04:35
【问题描述】:

我想从我的工作簿关闭事件中对我在 VBA 代码中实例化的一些第 3 方 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 2007 Zombie Process not COM automation but w/ references to 3rd party com objects,方法是确保在 Excel 退出之前正确处理这些第 3 方 COM 对象。

【问题讨论】:

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


    【解决方案1】:

    我没用过VBA,但我不认为它是一种.NET语言,所以自然不能在VBA中使用System.Runtime.InteropServices.Marshal之类的.NET Framework类。

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

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

    如果这不能解决问题,那么问题也可能是循环引用。 COM 对象是否存储了对您的 VBA 对象的引用,而 VBA 对象又持有对 COM 对象的引用?如果是这样,将创建循环引用并且永远不会释放对象。我正在搜索其他内容,发现与您的帖子非常相似:

    Forcing Garbage Collection

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

    其他几个类似的主题:

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

    【讨论】:

    • 谢谢。我会寻找循环引用,因为它最有可能是罪魁祸首。顺便说一句 - 您需要在相邻 URL 之间留一个空行,否则只有第一个 URL 将被转换为实时链接。 (我会为您修复它,但还没有必要的编辑权限。)
    • 不得不求助于创建 COM 包装器。我发布了我的解决方案并移动了“已接受”标志。
    【解决方案2】:

    即使在仔细检查并删除了循环引用之后,我也无法使用推荐的仅 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.InteropServices 和System.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,如果您无法访问客户端计算机上的注册表,这可能会很有用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多