为什么示例代码会访问 IUnknown 中已删除的内存?

Why does example code access deleted memory in IUnknown?

使用 IUnknown 等接口时有很多示例,在此示例中 IDocHostUIHandler 但这并不重要 - 使用类似于以下的代码:

 class TDocHostUIHandlerImpl : public IDocHostUIHandler
 {
 private:

    ULONG RefCount;

    public:      

    TDocHostUIHandlerImpl():RefCount(0){ }

    // IUnknown Method
    HRESULT __stdcall QueryInterface(REFIID riid, void **ppv) {
        if (IsEqualIID(riid,IID_IUnknown))
            {
            *ppv = static_cast<IUnknown*>(this);
            return S_OK;
            }
        else if (IsEqualIID(riid, IID_IDocHostUIHandler)) {
            *ppv = static_cast<IDocHostUIHandler*>(this);
            return S_OK;
            }
        else {
            *ppv = NULL;
            return E_NOINTERFACE;
            }
        }

    ULONG   __stdcall AddRef() {
        InterlockedIncrement((long*)&RefCount);
        return RefCount;
        }

    ULONG   __stdcall Release() {
        if (InterlockedDecrement((long*)&RefCount) == 0) delete this;
        return RefCount;
        }

我的问题是 Release() 方法使用 delete this 删除了接口实现,但紧接着 return RefCount 不再引用内存中的有效对象(它访问已删除的内存)。

我认为应该是这样的

    ULONG   __stdcall Release() {
        if (InterlockedDecrement((long*)&RefCount) == 0) { delete this; return 0; }
        return RefCount;
        }

这也不会触发我使用的资源泄漏工具(C++ Builder 中的 Codeguard)。那么为什么那么多示例使用第一个版本,我在这里缺少什么?

或者只是在 Release 方法结束后在另一个编译器(如 Visual Studio 中调用了“delete this”?

举几个例子:

https://www.codeproject.com/Articles/4758/How-to-customize-the-context-menus-of-a-WebBrowser

https://bbs.csdn.net/topics/20135139

是的,你是对的,这些例子写得不好。它们需要像您描述的那样编写:

ULONG __stdcall Release()
{
    if (InterlockedDecrement((long*)&RefCount) == 0) {
        delete this;
        return 0;
    }
    return RefCount;
}

然而,他们最好只 return 无论结果 InterlockedDecrement returns。正如@RaymondChen 在评论中指出的那样,这也解决了 RefCount 被另一个线程递减的问题,在达到 return 之前可能会破坏 this,例如:

ULONG __stdcall Release()
{
    ULONG res = (ULONG) InterlockedDecrement((long*)&RefCount);
    if (res == 0) {
        delete this;
    }
    return res;
}

AddRef() 相同,就此而言:

ULONG __stdcall AddRef()
{
    return (ULONG) InterlockedIncrement((long*)&RefCount);
}

附带说明一下,您显示的 QueryInterface() 示例也写错了,因为当 returning S_OK 时它没有递增 RefCount,例如:

HRESULT __stdcall QueryInterface(REFIID riid, void **ppv)
{
    if (IsEqualIID(riid,IID_IUnknown)) {
        *ppv = static_cast<IUnknown*>(this);
        AddRef(); // <-- add this!
        return S_OK;
    }
    else if (IsEqualIID(riid, IID_IDocHostUIHandler)) {
        *ppv = static_cast<IDocHostUIHandler*>(this);
        AddRef(); // <-- add this!
        return S_OK;
    }
    else {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
}

通常可以这样写更容易:

HRESULT __stdcall QueryInterface(REFIID riid, void **ppv)
{
    if (!ppv) {
        return E_POINTER;
    }

    if (IsEqualIID(riid, IID_IUnknown)) {
        *ppv = static_cast<IUnknown*>(this);
    }
    else if (IsEqualIID(riid, IID_IDocHostUIHandler)) {
        *ppv = static_cast<IDocHostUIHandler*>(this);
    }
    else {
        *ppv = NULL;
        return E_NOINTERFACE;
    }

    AddRef();
    return S_OK;
}

我也看到它是这样写的,它解释了在 NULL 指针上调用 QueryInterface() 时的不良情况:

HRESULT __stdcall QueryInterface(REFIID riid, void **ppv)
{
    if (!ppv) {
        return E_POINTER;
    }

    if (IsEqualIID(riid, IID_IUnknown)) {
        *ppv = static_cast<IUnknown*>(this);
    }
    else if (IsEqualIID(riid, IID_IDocHostUIHandler)) {
        *ppv = static_cast<IDocHostUIHandler*>(this);
    }
    else {
        *ppv = NULL;
    }

    if (*ppv) {
        AddRef();
        return S_OK;
    }

    return E_NOINTERFACE;
}