c++ - 为什么示例代码访问 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
解决方案
是的,你是对的,这样的例子写得不好。他们需要写得更像你描述的那样:
ULONG __stdcall Release()
{
if (InterlockedDecrement((long*)&RefCount) == 0) {
delete this;
return 0;
}
return RefCount;
}
但是,他们最好只返回返回的任何结果InterlockedDecrement
。正如@RaymondChen 在评论中指出的那样,这也解决了在到达之前RefCount
被另一个线程递减的问题,可能会破坏,例如:this
return
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;
}
推荐阅读
- sql - 查询地点和分组方式
- java - 数组列表
不会添加超过一项 - c++ - strncpy 后 char 数组的错误行为
- php - 如何在 php 中运行 powershell 命令列表?
- roslyn - 如何使用 Roslyn 将函数插入到 VB.NET 中的类中
- visual-studio-code - 禁用无标题编辑器改进 - 标题中的第一行名称
- java - 如何打印字符串的每三个字符?爪哇
- regex - 如何使用 RegEx 匹配 SQL Server 或 BigQuery 中的记录(标准方言)
- sql-server - T-SQL 生成 XML
- reactjs - AWS Cognito 中多种用户类型的最佳实践?