问题
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