首页 > 解决方案 > 为什么示例代码访问 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)中调用“删除这个”的情况?

几个例子:

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

IUnknown 中的 addref 和 release,它们实际上是做什么的?

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

标签: c++interfacec++builder

解决方案


是的,你是对的,这样的例子写得不好。他们需要写得更像你描述的那样:

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

但是,他们最好只返回返回的任何结果InterlockedDecrement。正如@RaymondChen 在评论中指出的那样,这也解决了在到达之前RefCount被另一个线程递减的问题,可能会破坏,例如:thisreturn

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()您显示的示例也写得不正确,因为它没有RefCount在返回时增加S_OK,例如:

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;
}

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

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;
}

推荐阅读