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

Posted

技术标签:

【中文标题】ATL 对象释放自身的安全位置【英文标题】:Safe place for an ATL object to release itself 【发布时间】:2021-08-19 23:24:56 【问题描述】:

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

换句话说,假设您有一个 ATL 类,它继承了某些窗口(使用消息映射)和/或接收来自 COM 对象的事件(使用接收器映射)。并且您希望课程在给定特定消息或事件时释放自己。例如,您可能希望在特定子类窗口收到WM_CLOSE 时释放,或者您正在下沉DWebBrowserEvents2 并希望在DISPID_ONQUIT 时释放。

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

有人知道解决这个问题的方法吗?感谢您的任何意见。

【问题讨论】:

【参考方案1】:

根据documentation for CWindowImpl::OnFinalMessage:

OnFinalMessage 的默认实现不执行任何操作,但您可以覆盖此函数以在销毁窗口之前进行清理。如果你想在窗口销毁时自动删除你的对象,你可以在这个函数中调用delete this;

所以看起来就是这样做的地方。

【讨论】:

非常感谢,但我认为这并不能解决问题。因为我主要关心子类窗口,而不是我自己的,以及接收事件。如果我自己的窗口被破坏,OnFinalMessage 就会被触发。 @user15025873 - 成功调用UnsubclassWindow 后,您需要通过m_dwState |= WINSTATE_DESTROYED; 将自己的窗口标记为已销毁,结果OnFinalMessage 将在您的类对象上调用【参考方案2】:

你可以做下一个实现

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 线程访问,否则需要使用原子/互锁操作)。当dwRefCount 变为0 时删除Release() 中的对象。当我们对窗口进行子类化时 - 添加额外的引用,并在 UnsubclassWindow - 将WINSTATE_DESTROYED 设置为状态位(m_dwState |= WINSTATE_DESTROYED) - 结果OnFinalMessage 将在最后一条消息之后被调用在这里我们已经调用了Release。或者如果我们在窗口被销毁之前不直接取消子类 - 无论如何 atl 实现最终都会调用 OnFinalMessage 我们释放自我的地方。

【讨论】:

以上是关于ATL 对象释放自身的安全位置的主要内容,如果未能解决你的问题,请参考以下文章

如何找到对象正在释放的位置

释放对象的引用

关于java内存释放的问题

自动释放池

iOS自动释放池_原理_如何工作

iOS自动释放池_原理_如何工作