ATL 对象释放自身的安全位置

Safe place for an ATL object to release itself

是否有一种策略可以安全地允许 ATL 对象释放自身以响应 Windows 消息或接收器事件?

换句话说,假设您有一个 ATL class 子classing 某些 windows(使用消息映射)and/or 来自 COM 对象的下沉事件(使用汇图)。您希望 class 在收到特定消息或事件时自行释放。例如,您可能希望在特定子 classed window 收到 WM_CLOSE 时发布,或者您正在下沉 DWebBrowserEvents2 并希望在 DISPID_ONQUIT 时发布.

我认为问题在于,如果您在消息或事件处理程序的中间释放,ATL 框架之后可能仍有一些处理要做(即使您,比如说,做类似 bHandled = TRUE ).如果那时你的对象已经released/deleted,那么坏事就会接踵而至。

有谁知道解决这个问题的方法吗?感谢您的任何输入。

根据 documentation for CWindowImpl::OnFinalMessage:

The default implementation of OnFinalMessage does nothing, but you can override this function to handle cleanup before destroying a window. If you want to automatically delete your object upon the window destruction, you can call delete this; in this function.

所以这看起来是个合适的地方。

你可以做下一个实现

class MySubClass : public CWindowImplBaseT<>
{
    ULONG dwRefCount = 1;

    BEGIN_MSG_MAP(MySubClass)
        MESSAGE_HANDLER(WM_CHAR, OnChar)
    END_MSG_MAP()

    LRESULT OnChar(UINT uMsg, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
    {
        bHandled = FALSE;

        // for instance unsublass when ESC pressed
        // but possible do this on any event
        if (uMsg == WM_CHAR && wParam == VK_ESCAPE)
        {
            UnsubclassWindow(FALSE);
        }

        return 0;   
    }

    virtual void OnFinalMessage(_In_ HWND hWnd)
    {
        __super::OnFinalMessage(hWnd);
        Release();
    }

    ~MySubClass()
    {
        DbgPrint("%s<%p>\n", __FUNCTION__, this);
    }

public:

    BOOL SubclassWindow(_In_ HWND hWnd)
    {
        if (__super::SubclassWindow(hWnd))
        {
            AddRef();
            return TRUE;
        }

        return FALSE;
    }

    HWND UnsubclassWindow(_In_ BOOL bForce /*= FALSE*/)
    {
        if (HWND hwnd = __super::UnsubclassWindow(bForce))
        {
            // mark window as destroyed
            m_dwState |= WINSTATE_DESTROYED;
            m_hWnd = hwnd;
            return hwnd;
        }

        return 0;
    }

    void AddRef()
    {
        dwRefCount++;
    }

    void Release()
    {
        if (!--dwRefCount)
        {
            delete this;
        }
    }
};

void DoSubclass(HWND hwnd)
{
    if (MySubClass* pObj = new MySubClass)
    {
        pObj->SubclassWindow(hwnd);

        pObj->Release();
    }
}

所以关键点 - 对对象有引用计数(ULONG dwRefCount - 这里我假设对象只能从单个 UI 线程访问,否则需要使用 atomic/interlocked 操作)。当 dwRefCount 变为 0 时删除 Release() 中的对象。当我们对 window 进行子类化时 - 添加附加引用,并在 UnsubclassWindow 中 - 在状态中设置 WINSTATE_DESTROYED 位( m_dwState |= WINSTATE_DESTROYED ) - 结果 OnFinalMessage 将在最后一条消息后被调用,这里我们已经调用了 Release。或者如果我们在 window 被销毁之前不直接取消子类 - atl 实现无论如何最终调用 OnFinalMessage 我们释放自我的地方。