CCmdTarget::OnFinalRelease 在啥条件下被调用?

Posted

技术标签:

【中文标题】CCmdTarget::OnFinalRelease 在啥条件下被调用?【英文标题】:Under what conditions is CCmdTarget::OnFinalRelease called?CCmdTarget::OnFinalRelease 在什么条件下被调用? 【发布时间】:2011-10-14 07:30:32 【问题描述】:

CCmdTarget::OnFinalRelease 方法的 MSDN 文档非常简短:

当最后一个 OLE 引用到或来自 对象被释放。

我创建了一个 CCmdTarget 的子类

class CMyEventHandler : public CCmdTarget  ... 

我试图弄清楚 OnFinalRelease 方法将在什么条件下被调用。我有一些看起来像这样的代码:

CMyEventHandler* myEventHandler = new CMyEventHandler();
LPUNKNOWN pUnk = myEventHandler->GetIDispatch(FALSE);
AfxConnectionAdvise(myEventSource, DIID_IMyEventInterface, pUnk, FALSE, myCookie);

// Application continues...events arrive...eventually the event sink is shutdown

LPUNKNOWN pUnk = myEventHandler->GetIDispatch(FALSE);
AfxConnectionUnadvise(myEventSource, DIID_IMyEventInterface, pUnk, FALSE, myCookie);

使用此代码,我观察到 OnFinalRelease 方法从未被调用。这意味着我有内存泄漏。所以我修改了总结代码如下:

LPUNKNOWN pUnk = myEventHandler->GetIDispatch(FALSE);
AfxConnectionUnadvise(myEventSource, DIID_IMyEventInterface, pUnk, FALSE, myCookie);
delete myEventHandler;
myEventHandler = NULL;

这部分代码会在一天中定期触发。我现在注意到的是,虽然 myEventHandler 的包装实例的析构函数按预期调用,但 OnFinalRelease 函数现在被调用了!更糟糕的是,它不是在已包装的实例上调用,而是在新创建的 CMyEventHandler 实例上调用!考虑到这可能是由于引用计数问题,我修改了连接和总结代码:

CMyEventHandler* myEventHandler = new CMyEventHandler();
LPUNKNOWN pUnk = myEventHandler->GetIDispatch(TRUE);
AfxConnectionAdvise(myEventSource, DIID_IMyEventInterface, pUnk, TRUE, myCookie);
pUnk->Release();

// Application continues...events arrive...eventually the event sink is shutdown

LPUNKNOWN pUnk = myEventHandler->GetIDispatch(TRUE);
AfxConnectionUnadvise(myEventSource, DIID_IMyEventInterface, pUnk, TRUE, myCookie);
pUnk->Release();
delete myEventHandler;
myEventHandler = NULL;

我让它运行一整天,现在观察到 OnFinalRelease 从未被调用过。包装实例的析构函数按我的预期调用,但我感到不安,因为我显然不了解调用 OnFinalRelease 的情况。 OnFinalRelease 是否会延迟调用,还是有办法强制它触发?什么会触发 OnFinalRelease 被调用?

如果重要的话,事件源是一个通过 COM 互操作公开事件的 .NET 程序集。

【问题讨论】:

【参考方案1】:

对于 COM,您应该始终使用 CoCreateInstance() AddRef() 和 Release() 范例来管理对象的生命周期,并让 COM 根据引用计数来销毁对象。避免使用 new 和 delete,因为使用它们会破坏这种范式并导致有趣的副作用。您可能在引用计数的管理中存在错误。

调试引用计数未正确管理的方法是覆盖 CCmdTarget::InternalRelease() 从 oleunk.cpp 复制源并放置一些跟踪输出或断点。

DWORD CMyEventHandler::InternalRelease()

    ASSERT(GetInterfaceMap() != NULL);

    if (m_dwRef == 0)
        return 0;

    LONG lResult = InterlockedDecrement(&m_dwRef);
    if (lResult == 0)
    
        AFX_MANAGE_STATE(m_pModuleState);
        OnFinalRelease();
    
    return lResult;

很多时候,当传递 IDispatch 接口时,代码会增加引用计数,您必须使用 Release() 减少引用计数。注意你的代码可能在哪里传递这个接口,因为 COM 中有一个约定,当使用 [in] 或 [out] 传递接口时,调用者或被调用者必须释放接口。

当引用计数问题得到纠正时,您应该看到对象 OnFinalRelease 代码被调用以及被 hte MFC 框架破坏的对象:

对于 CCmdTarget,销毁应该是作为最终结果的结果 在父类CWnd中发布:

void CWnd::OnFinalRelease()

    if (m_hWnd != NULL)
        DestroyWindow();    // will call PostNcDestroy
    else
        PostNcDestroy();

仅供参考:跨线程传递接口而不编组接口指针是在 COM 中出错的另一个常见原因。

【讨论】:

【参考方案2】:

您似乎从未致电myEventHandler->Release()。因此,最后一个引用永远不会被释放,OnFinalRelease 永远不会被调用。

【讨论】:

我的印象是第一个 AddRef 发生在调用 GetIDispatch 时,而不是在构造时发生,因此无需在 myEventHandler 上调用 Release。这不正确吗? 在构建后立即查看CCmdTargetm_dwRef 成员。我敢打赌它是 1。(或者您可以查看 MFC 源代码。MFC 附带源代码。)

以上是关于CCmdTarget::OnFinalRelease 在啥条件下被调用?的主要内容,如果未能解决你的问题,请参考以下文章