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

Why does example code access deleted memory in IUnknown?

本文关键字:删除 内存 IUnknown 代码 访问 为什么      更新时间:2023-10-16

当使用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中寻址和释放,它们实际上是做什么的?

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

是的,这样的例子写得不好是正确的。它们需要写得更像你所描述的:

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

然而,对于他们来说,最好只返回InterlockedDecrement返回的任何结果。正如@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()示例也写错了,因为它在返回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;
}