Why does example code access deleted memory in IUnknown?

匆匆过客 提交于 2020-07-23 04:56:08

问题


Quite a number of examples when using interfaces such as IUnknown, in this example IDocHostUIHandler but it doesn't really matter - use code similar to this:

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

My problem here is with the Release() method which deletes the interface implementation using delete this but immediately after that does return RefCount which no longer refers to a valid object in memory (it accesses deleted memory).

I would assume that it should be something like

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

Which also doesn't trigger resource leak tool which I use (Codeguard in C++ Builder). So why then so many examples use the first version, what am I missing here?

Or is it just the case that the "delete this" is called in another compiler like Visual Studio after the Release method ends?

A few examples:

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

addref and release in IUnknown, what do they actually do?

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


回答1:


Yes, you are correct that such examples are badly written. They need to be written more like you have described:

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

However, it is better for them to just return whatever result InterlockedDecrement returns. As @RaymondChen pointed out in comments, this also addresses the issue of RefCount being decremented by another thread, potentially destroying this, before the return is reached, eg:

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

Same with the AddRef(), for that matter:

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

On a side note, the QueryInterface() example you have shown is also written incorrectly, as it is not incrementing the RefCount when returning S_OK, eg:

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

Which can typically be written more easily like this instead:

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

I've also seen it written like this, which accounts for bad cases when QueryInterface() is called on a NULL pointer:

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


来源:https://stackoverflow.com/questions/62885782/why-does-example-code-access-deleted-memory-in-iunknown

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!