如何在 Release() 上释放 NET COM 互操作对象

2023-11-26

我有一个用托管代码 (C++/CLI) 编写的 COM 对象。我在标准 C++ 中使用该对象。
当 COM 对象被释放时,如何强制立即调用 COM 对象的析构函数?如果不可能,请调用 I have Release() 在我的 COM 对象上调用 MyDispose() 方法?

我声明对象的代码 (C++/CLI):



    [Guid("57ED5388-blahblah")]
    [InterfaceType(ComInterfaceType::InterfaceIsIDispatch)]
    [ComVisible(true)]
    public interface class IFoo
    {
        void Doit();
    };

    [Guid("417E5293-blahblah")]
    [ClassInterface(ClassInterfaceType::None)]
    [ComVisible(true)]
    public ref class Foo : IFoo
    {
    public:
        void MyDispose();
        ~Foo() {MyDispose();} // This is never called
        !Foo() {MyDispose();} // This is called by the garbage collector.
        virtual ULONG Release() {MyDispose();} // This is never called
        virtual void Doit();
    };
  

我使用该对象的代码(本机 C++):



#import "..\\Debug\\Foo.tlb"
...
Bar::IFoo setup(__uuidof(Bar::Foo)); // This object comes from the .tlb.
setup.Doit();
setup->Release(); // explicit release, not really necessary since Bar::IFoo's destructor will call Release().
  

如果我在 COM 对象上放置析构函数方法,则永远不会调用它。如果我放置一个终结器方法,当垃圾收集器开始处理它时就会调用它。如果我显式调用 Release() 覆盖,它就永远不会被调用。

我真的很喜欢它,这样当我的本机 Bar::IFoo 对象超出范围时,它会自动调用我的 .NET 对象的处置代码。我认为我可以通过重写 Release() 来做到这一点,如果对象计数 = 0,则调用 MyDispose()。但显然我没有正确覆盖 Release(),因为我的 Release() 方法从未被调用。

显然,我可以通过将 MyDispose() 方法放入接口中并要求使用我的对象的人在 Release() 之前调用 MyDispose() 来实现这一点,但如果 R​​elease() 只是清理对象,那就更顺利了。

当 COM 对象被释放时,是否可以强制立即调用 .NET COM 对象的析构函数或其他方法?

谷歌搜索这个问题后,我得到了很多结果,告诉我调用 System.Runtime.InteropServices.Marshal.ReleaseComObject(),但当然,这就是告诉 .NET 释放 COM 对象的方式。我想要 COM Release() 来处理 .NET 对象。


我有一个用托管代码 (C++/CLI) 编写的 COM 对象。我在标准 C++ 中使用该对象。 当 COM 对象被释放时,如何强制立即调用 COM 对象的析构函数?如果不可能,我可以让 Release() 在我的(托管 DotNet - GBG)COM 对象上调用 Dispose() (不是 MyDispose() - GBG)方法吗?

RE: 当客户端是非托管代码时,强制确定性地释放 DotNet COM Server 绑定的资源。这些资源可以安排在垃圾收集器收集项目时释放,但这不是确定性的,对于垃圾收集不频繁的大型内存系统,文件流等资源可能数小时或数天都不会释放。

这是 COM Callable Wrappers (CCW) 的一个常见问题,可以通过另一个相关的线程看到:是否可以拦截(或了解)对暴露给 COM 的 CLR 对象进行 COM 引用计数。在这种情况下,就像在编写自己的 COM 客户端的任何情况下一样,无论是在托管代码还是非托管代码下,只需调用 IDisposable.Dispose() 方法即可轻松解决,就像在那里所做的那样。但是,该方法不适用于(例如)DotNet COM 编解码器类,该类的客户端可能是操作系统本身,并且客户端不必知道 COM 服务器是非托管或托管 (DotNet)。

可以按照 MSDN 链接在 DotNet COM 服务器上实现 IDisposable.Dispose() 模式:http://msdn.microsoft.com/en-us/library/system.idisposable.aspx,但这不会有任何好处,因为 Dispose() 方法永远不会被 CCW 调用。理想情况下,如果作为 CCW 发布和/或终结/析构函数的一部分实现,则 mscoree.dll 中 CCW 的实现应该真正检查并调用 IDisposable.Dispose() 方法。我不确定为什么 Microsoft 不这样做,因为可以完全访问程序集信息,他们可以轻松确定 DotNet COM 类是否支持 IDisposable,如果支持,只需在最终版本上调用 Dispose() 即可,因为这将在CCW 中,由于额外的接口引用而导致的处理引用计数的所有复杂性都可以避免。

我看不出这会如何“破坏”任何现有代码,因为任何识别 IDisposable 的客户端仍然可以调用 Dispose(),如果根据上面的模板实现,它只会在第一次调用时有效地执行任何操作。微软可能会担心一个类被处置,而仍然存在对它的托管引用,直到尝试使用已处置的资源开始引发异常时,这些引用才知道它被处置,但这对于任何不当行为都是一个潜在的问题即使仅使用 DotNet 客户端,也可使用 IDisposable 接口:如果对同一对象实例有多个引用,并且其中任何一个调用 Dispose(),则其他引用会发现尝试使用所需的已处置资源会导致异常。对于这种情况,应该始终使用处置布尔值(根据 IDisposable 模式模板)放置防护,或者仅通过公共包装器引用对象。

由于 Microsoft 尚未完成在 mscoree.dll 中实现 CCW 所需的几行代码,因此我在 mscoree.dll 周围编写了一个包装器来添加此额外功能。它有点复杂,因为为了控制围绕任何 DotNet COM 类的任何实例创建包装器,我还需要包装 IClassFactory 接口并将 CCW 实例聚合在我的“CCW_Wrapper”包装器类中。该包装器还支持来自另一个外部类的更高级别的聚合。该代码还对正在使用的 mscoree.dll 实现中的类实例进行引用计数,以便能够在没有引用时调用 mscoree.dll 上的 FreeLibrary(如果需要,稍后可以再次调用 LoadLibrary)。该代码还应该是多线程友好的,正如 Windows 7 下 COM 所需要的那样。我的 C++ 代码如下:

2010 年 12 月 22 日编辑:消除了 COM_Wrapper 构造函数的一个不必要的参数:

#include <windows.h>

HMODULE g_WrappedDLLInstance = NULL;
ULONG g_ObjectInstanceRefCnt = 0;

//the following is the C++ definition of the IDisposable interface
//using the GUID as per the managed definition, which never changes across
//DotNet versions as it represents a hash of the definition and its
//namespace, none of which can change by definition.
MIDL_INTERFACE("805D7A98-D4AF-3F0F-967F-E5CF45312D2C")
    IDisposable : public IDispatch {
    public:
        virtual VOID STDMETHODCALLTYPE Dispose() = 0;
    };

class CCW_Wrapper : public IUnknown {
public:
    // constructor and destructor
    CCW_Wrapper(
        __in IClassFactory *pClassFactory,
        __in IUnknown *pUnkOuter) :
            iWrappedIUnknown(nullptr),
            iOuterIUnknown(pUnkOuter),
            iWrappedIDisposable(nullptr),
            ready(FALSE),
            refcnt(0) {
        InterlockedIncrement(&g_ObjectInstanceRefCnt);
        if (!this->iOuterIUnknown)
            this->iOuterIUnknown = static_cast<IUnknown*>(this);
        pClassFactory->CreateInstance(
            this->iOuterIUnknown,
            IID_IUnknown,
            (LPVOID*)&this->iWrappedIUnknown);
        if (this->iWrappedIUnknown) {
            if (SUCCEEDED(this->iWrappedIUnknown->QueryInterface(__uuidof(IDisposable), (LPVOID*)&this->iWrappedIDisposable)))
                this->iOuterIUnknown->Release(); //to clear the reference count caused by the above.
        }
        this->ready = TRUE; //enable destruction of the object when release decrements to zero.
        //OUTER IUNKNOWN OBJECTS MUST ALSO PROTECT THEIR DESTRUCTORS IN SIMILAR MANNERS!!!!!
    }
    ~CCW_Wrapper() {
        this->ready = FALSE; //protect from re-entering this destructor when object released to zero.
        if (this->iWrappedIDisposable) {
            //the whole reason for this project!!!!!!!!
            this->iWrappedIDisposable->Dispose();
            //the following may be redundant, but to be sure...
            this->iOuterIUnknown->AddRef();
            this->iWrappedIDisposable->Release();
        }
        if (this->iWrappedIUnknown)
            this->iWrappedIUnknown->Release();
        if (!InterlockedDecrement(&g_ObjectInstanceRefCnt)) {
            //clear all global resources including the mutex, multithreading safe...
            HMODULE m = (HMODULE)InterlockedExchangePointer((PVOID*)&g_WrappedDLLInstance, (LPVOID)0);
            if (m)
                FreeLibrary(m);
        }
    }

    // IUnknown Interface
    STDMETHOD(QueryInterface)(REFIID riid, void **ppv) {
        if (ppv) {
            *ppv = nullptr;
            if (riid == IID_IUnknown) {
                *ppv = static_cast<IUnknown*>(this);
                this->AddRef();
                return S_OK;
            }
            else if (this->iWrappedIUnknown) {
                return this->iWrappedIUnknown->QueryInterface(riid, ppv);
            }
            return E_NOINTERFACE;
        }
        return E_INVALIDARG;
    }

    STDMETHOD_(ULONG, AddRef)() {
        return InterlockedIncrement(&this->refcnt);    
    }

    STDMETHOD_(ULONG, Release)() {
        if (InterlockedDecrement(&this->refcnt))
            return this->refcnt;
        if (this->ready) //if not being constructed or destructed...
            delete this;
        return 0;
    }

private:
    IUnknown *iOuterIUnknown;
    IUnknown *iWrappedIUnknown;
    IDisposable *iWrappedIDisposable;
    BOOL ready;
    ULONG refcnt;
};

class ClassFactoryWrapper : public IClassFactory {
public:
    // constructor and destructor
    ClassFactoryWrapper(IClassFactory *icf) : wrappedFactory(icf), refcnt(0), lockcnt(0) {
        InterlockedIncrement(&g_ObjectInstanceRefCnt);
    }
    ~ClassFactoryWrapper() {
        if (wrappedFactory)
            wrappedFactory->Release();
        if (!InterlockedDecrement(&g_ObjectInstanceRefCnt)) {
            //clear all global resources, multithreading safe...
            HMODULE m = (HMODULE)InterlockedExchangePointer((PVOID*)&g_WrappedDLLInstance, (LPVOID)0);
            if (m)
                FreeLibrary(m);
        }
    }

    // IUnknown Interface
    STDMETHOD(QueryInterface)(REFIID riid, void **ppv) {
        if (ppv) {
            *ppv = nullptr;
            if (riid == IID_IUnknown) {
                *ppv = static_cast<IUnknown*>(this);
                this->AddRef();
            }
            else if (riid == IID_IClassFactory) {
                *ppv = static_cast<IClassFactory*>(this);
                this->AddRef();
            }
            else {
                return E_NOINTERFACE;
            }
            return S_OK;
        }
        return E_INVALIDARG;
    }

    STDMETHOD_(ULONG, AddRef)() {
        return InterlockedIncrement(&this->refcnt);    
    }

    STDMETHOD_(ULONG, Release)() {
        if (InterlockedDecrement(&this->refcnt) || this->lockcnt)
            return this->refcnt;
        delete this;
        return 0;
    }

    // IClassFactory Interface
    STDMETHOD(CreateInstance)(IUnknown *pUnkOuter, REFIID riid, void **ppv) {
        HRESULT result = E_INVALIDARG;

        if (ppv) {
            *ppv = nullptr;
            if (pUnkOuter && (riid != IID_IUnknown))
                return result;
            CCW_Wrapper *oipm = new CCW_Wrapper(wrappedFactory, pUnkOuter);
            if (!oipm)
                return E_OUTOFMEMORY;
            if (FAILED(result = oipm->QueryInterface(riid, ppv)))
                delete oipm;
        }

        return result;
    }

    STDMETHOD(LockServer)(BOOL fLock) {
        if (fLock)
            InterlockedIncrement(&this->lockcnt);
        else {
            if (!InterlockedDecrement(&this->lockcnt) && !this->refcnt)
                delete this;
        }
        return wrappedFactory->LockServer(fLock);
    }

private:
    IClassFactory *wrappedFactory;
    ULONG refcnt;
    ULONG lockcnt;
};


STDAPI DllGetClassObject(__in REFCLSID rclsid, __in REFIID riid, __deref_out LPVOID FAR* ppv) {
    HRESULT result = E_INVALIDARG;

    if (ppv) {
        *ppv = nullptr;
        if ((riid != IID_IUnknown) && (riid != IID_IClassFactory))
            return E_NOINTERFACE;
        HMODULE hDLL = LoadLibrary(L"mscoree.dll");
        if (!hDLL)
            return E_UNEXPECTED;
        typedef HRESULT (__stdcall *pDllGetClassObject) (__in REFCLSID, __in REFIID, __out LPVOID *);
        pDllGetClassObject DllGetClassObject = (pDllGetClassObject)GetProcAddress(hDLL, "DllGetClassObject");
        if (!DllGetClassObject) {
            FreeLibrary(hDLL);
            return E_UNEXPECTED;
        }
        IClassFactory *icf = nullptr;
        if (FAILED(result = (DllGetClassObject)(rclsid, IID_IClassFactory, (LPVOID*)&icf))) {
            FreeLibrary(hDLL);
            return result;
        }
        ClassFactoryWrapper *cfw = new ClassFactoryWrapper(icf);
        if (!cfw) {
            icf->Release();
            FreeLibrary(hDLL);
            return E_OUTOFMEMORY;
        }
        //record the HMODULE instance in global variable for freeing later, multithreaded safe...
        hDLL = (HMODULE)InterlockedExchangePointer((PVOID*)&g_WrappedDLLInstance, (LPVOID)hDLL);
        if (hDLL)
            FreeLibrary(hDLL);
        if (FAILED(result = cfw->QueryInterface(IID_IClassFactory, ppv)))
            delete cfw; //will automatically free library and the held class factory reference if necessary.
    }
    return result;    
}

extern "C"
HRESULT __stdcall DllCanUnloadNow(void) {
    if (g_ObjectInstanceRefCnt)
        return S_FALSE;
    return S_OK;
}

extern "C"
BOOL APIENTRY DllMain( HMODULE hModule,
                                                DWORD  ul_reason_for_call,
                                                LPVOID lpReserved ) {
    switch (ul_reason_for_call) {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
    }
    return TRUE;
}

DLL 还需要一个“.def”文件,如下所示:

LIBRARY mscoreeCOM_DisposeWrapper

EXPORTS
    DllCanUnloadNow         PRIVATE
    DllGetClassObject       PRIVATE

要使用此源代码,请将其编译为 DLL 并将 DLL 安装到 Windows SYSTEM 文件夹中,然后让安装程序或 DotNet COM 服务器中的 [COMRegisterFunction] 方法将 InprocServer32 的类注册表项从 mscoree.dll 修改为该包装器的名称(例如 mscoreeWrapper.dll)。它可以在32位和/或64位下编译,如果要在64位系统上安装,请将64位版本放入System文件夹中,将32位版本放入SysWOW64文件夹中;此外,正常的 CLSID 注册和虚拟化 WOW6432 版本都应针对 InprocServer32 条目进行修改。某些应用程序可能需要对该包装 DLL 进行数字签名才能无缝工作,这完全是另一个主题。如果有人需要,我将在此处提供这些 DLL 的编译版本的链接。

正如我所说,所需的几行(不包括包装器要求)技术确实应该合并到 mscoree.dll 中。有谁知道如何联系 Microsoft 相应部门的人员提出此建议?

EDITADD:我已经提交了一份建议用于将 DotNet 框架连接到 Microsoft Connect。这似乎是向微软提供反馈的最佳方式。

编辑DD2:在实现此问题的解决方法时,我意识到为什么 MIcrosoft 可能不会实现“当 CCW 引用计数降至零时,如果支持的话,自动调用 Dispose”。在编写解决方法时,我必须获取指向托管对象上的 COM 接口的引用指针,以便将其传递给纯非托管 COM 方法,然后必须 Release() 该引用计数,以便不强烈使用 CCW引用该对象,因此由于永远无法将其用于垃圾回收而导致内存泄漏。我这样做是因为我知道当前,将托管对象上的引用计数减少到零只会使 CCW 删除对该对象的强引用,如果没有其他引用,则使其有资格进行垃圾回收。但是,如果 Microsoft 按照我的建议实现了 Auto Dispose 修复,或者如果此代码已就位包装了 mscoree.dll 功能,则这会在不需要时在托管对象上触发 Dispose()。对于这种特殊情况,我可以“保护” Dispose(bool dispose) 虚拟方法以防止 Dispose() 发生,但对于在相同假设下使用此行为的任何现有代码,包括 Microsoft 的 DotNet 运行时库的实现,在 CCW 上实现此“修复”将破坏现有代码。此包装器修复仍然适用于自己编写的 COM 服务器,并意识到这种副作用,因为它们可以在 Dispose() 上放置“防护”。

编辑3:在进一步的工作中,我发现我向 Microsoft 提出的建议仍然有效,并且可以通过在实现托管 COM 服务器的对象实例上调用 IDisposable.Dispose() 方法的修复来避免“破坏”现有代码的问题。如果接口存在仅当新的自定义属性(例如 [AutoComDispose(true)])(其默认值为 false)应用于托管 COM 服务器类时。通过这种方式,程序员将选择实现该功能,并且有关新属性的文档将警告其使用时必须“保护”Dispose() 方法,例如在可能出现以下情况时使用“人工引用计数” Marshal.Release() 方法由托管服务器使用的代码显式调用,或者通过调用 Marshal.GetObjectForIUnknown() 等方法隐式调用,在某些情况下,如果 ComObject 是托管的,则可以调用参考点的 QueryInterface 和 Release目的。

该答案的主要问题是安装它以供使用的复杂性,如上所述。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 Release() 上释放 NET COM 互操作对象 的相关文章

随机推荐

  • 错误:未找到名称“ngModel”的导出

    构建我的角度项目后 我收到错误 错误 未找到名称 ngModel 的导出 我的 UI 在 Docker 容器中运行 甚至不知道在哪里寻找这个 它在开发中工作正常 发球 有任何想法吗 我有同样的错误 尽管在开发中 事实证明我没有添加表单模块模
  • 如何从 Google Analytics 获取原始日志?

    是否可以从 Google Analytic 获取原始日志 有没有可以从GA生成原始日志的工具 不 您无法获取原始日志 但没有什么可以阻止您将完全相同的数据记录到您自己的 Web 服务器日志中 看看顽童代码并借用它 将以下两行更改为指向您的
  • 如何创建 AND 或 OR 表达式?

    我写了这个 if a 11 b 1 if a 1 AND b 1 但两者都不起作用 我也有同样的问题OR 如何编写包含以下内容的表达式OR or AND You use 对于 和 以及 为 或
  • 如何将 JavaScript onClick 处理程序添加到嵌入的 html 对象?

    我正在尝试将 onClick 处理程序添加到嵌入对象中 处理程序需要执行外部 js 文件中的函数 该文件通过链接到当前 html 文件button svg id buttonEmbed width 95 height 53 type ima
  • keras LSTM 层训练时间太长

    每当我在 Keras 上尝试 LSTM 模型时 似乎由于训练时间过长 该模型无法训练 例如 像这样的模型每步需要 80 秒来训练 def create model self inputs inputs input lstm placehol
  • 基于 TCP 的 WebRTC 媒体?

    我是 WebRTC 新手 我了解了回合服务器 下面的内容用于为 webrtc 应用程序配置基于 TCP 的 Turn 服务器 webrtc 应用程序中的转向服务器配置示例 url turn 192 158 29 39 3478 transp
  • 从十六进制值检测相似的颜色

    有谁知道一种获取两个十六进制颜色值并返回某种索引来说明颜色有多相似的方法 例如 两种黄色色调可能会返回更高的指数 即它们比灰色和黄色更相似 我正在使用 javascript 但我猜这样的东西将是一个独立于语言的公式 算法 可以从以下算法开始
  • 如何在 Mono For Android 中将位图转换为字节数组

    我正在使用 Mono for Android 我想将位图保存到字节数组 所以我可以将它保存到数据库中 在这里搜索我发现了以下代码 ByteArrayOutputStream bos new ByteArrayOutputStream bit
  • Firebase 查询子级的子级是否包含值

    表的结构是 chats gt 随机ID gt gt 参与者 gt gt gt 0 名称1 gt gt gt 1 名称2 gt gt 聊天项目 etc 我想做的是查询聊天表 以查找通过传入的用户名字符串容纳参与者的所有聊天 这是我到目前为止所
  • Kafka高级消费者使用Java API从主题获取所有消息(相当于--from-beginning)

    我正在使用 Kafka 站点上的 ConsumerGroupExample 代码测试 Kafka High Level Consumer 我想检索 Kafka 服务器配置中有关 测试 主题的所有现有消息 查看其他博客 auto offset
  • 在多个 GridViewColumn 中使用通用 DataTemplate

    我有一个显示一些值的 GridView
  • 跨平台方式检测符号链接/连接点?

    在java中 可以通过比较文件的规范路径和绝对路径来检测Unix环境中的符号链接 然而 这个技巧在 Windows 上不起作用 如果我执行 mkdir c foo mklink j c bar 从命令行 然后在java中执行以下几行 Fil
  • 怪异模式与 2011 年相关吗?

    随着IE9 FF4 不断更新的chrome等所有最新的浏览器 我们还需要怪异模式吗 如果有的话有什么用处 在什么场景下 Quirks 模式旨在允许 很多 较旧的网站在 相对 较新的浏览器中运行 新的开发永远不应该在 Quirks 模式下进行
  • ListView 图像的 onClick 侦听器 - Android

    我有一个ListView右侧有图像 我想表演一个onClick通过单击图像上的侦听器事件ListView 请参阅图片以供参考 我知道基本的OnClick监听器实现 但这对我来说似乎有点棘手 P 忘了说了 点击实际ListView将启动一项新
  • 使用 DbContext.Database.SqlQuery 在 EntityFramework 中进行预加载

    在 EF 4 中 我可以通过编写 sql 来预先加载导航属性吗DbContext Database SqlQuery or DbContext Set
  • 在 Mac OS 10.11 (El Capitan) 上使用 pfctl 转发端口

    我目前正在测试我的开发环境是否可以在即将推出的新 Mac OS 10 11 上运行 以及是否可以在发布后立即升级 在我的测试机器上 我当前正在运行 Beta Preview 3 一切似乎都运行良好 我只能得到pfctl转发我的端口 我使用
  • Angular 模板中的 Javascript 广告

    我正在尝试在 Angular 模板中呈现 Javascript 广告 但它不会显示 当他们将 Javascript 附加到 head 标签时 我找到了一些解决方案 但我希望将广告放置在我的 Html 正文内 中 这是一个笨蛋 https p
  • TestNG 选项未显示在 Eclipse 的 RunAs 选项中

    我在 Eclipse IDE 中使用 Maven 项目 并添加了一个 testng 依赖项
  • 如何使用网络浏览器在链接列表中导航?

    我有一个网址列表 我需要导航它们 如何确保每个 url 都会调用 DocumentCompleted 事件 我已经尝试创建许多线程并尝试使用单个线程 但应用程序仍然没有为每个 url 触发事件 DocumentCompleted 有没有办法
  • 如何在 Release() 上释放 NET COM 互操作对象

    我有一个用托管代码 C CLI 编写的 COM 对象 我在标准 C 中使用该对象 当 COM 对象被释放时 如何强制立即调用 COM 对象的析构函数 如果不可能 请调用 I have Release 在我的 COM 对象上调用 MyDisp