ATL C++ ccomobjects 的 safearray 造成内存泄漏
我发现自己需要帮助。现在,我对 C++ 并不是那么陌生,但是将它与 ATL 结合起来会带来全新的混乱。无论如何,我的问题是:我(最终)设法将 COM 方法中的对象数组返回给 C# 调用者。但在“测试”(重复运行该函数多次)后,我发现了一个小内存泄漏。
IDL 摘录:
...
interface IDISControl : IDispatch{
...
[id(12)] HRESULT GetNets([out,retval] VARIANT* nets);
};
标头摘录:
...
STDMETHOD(GetNets)(VARIANT* nets);
...
代码:
STDMETHODIMP CDISControl::GetNets(VARIANT* nets)
{
SNet *netz;
int32_t num;
int result, i;
result = DIS_GetNetNum(securityHandle, &num);
netz = new SNet[num];
result = DIS_GetNet(securityHandle, netz, num); //getting some data
CComSafeArray<IDispatch*> netArray;
CComObject<CDISNet> *net;
CComVariant *var;
netArray.Create(num, 0);
for (i = 0;i<num;i++){
CComObject<CDISNet>::CreateInstance(&net);
if (net == NULL)
return S_FALSE;
net->AddRef();
net->Convert(netz[i]);
netArray[i] = net;
net->Release();
net = NULL;
}
CComVariant val(netArray.Detach());
val.Detach(nets);
delete [] netz;
netArray.Destroy();
return S_OK;
}
我实例化 CDISNet 对象并在其中放入一些数据 (Convert())。我将它们放入我的安全数组中并释放。据我了解,销毁它们的责任转移到了 safearray 上。然后,我将数组放入 VARIANT 中,这样我就可以填充我的 [out, retval] 参数。因为它是一个输出参数,所以销毁的责任应该转移给调用者(在我的例子中是 C#,即它的 GarbageCollector)。我处理了我的动态数组“netz”并销毁了 safearray 包装器。
那么我错过了什么?剩下分配的是什么? (这个项目确实让我体会到了 .net 的所有便利)。
帮助。请。
编辑:进一步的调试向我表明问题肯定出在我的 CComObject 对象中。他们不会被解除分配。如果我在每次迭代中delete net;
,数组也会丢失数据。我不确定如何纠正这个问题......
编辑2: 好吧,我稍微研究了一下这段代码,当我注释掉变体装箱时,泄漏似乎就消失了。问题是我从 safearrays 上的 Visual Studio 示例借用了这段代码。那么,有谁知道发生了什么:
CComVariant val(netArray.Detach());
val.Detach(nets);
......以及该怎么办?
I find myself in need of help. Now, I'm not all that unfamiliar with C++, but combining it with ATL provides a whole new level of confusion. Anyways, my problem: I (finally) managed to return an array of objects in my COM method to C# caller. But upon 'testing' (running said function a number of times repeatedly) I recognized a small memory leak.
IDL excerpt:
...
interface IDISControl : IDispatch{
...
[id(12)] HRESULT GetNets([out,retval] VARIANT* nets);
};
Header excerpt:
...
STDMETHOD(GetNets)(VARIANT* nets);
...
Code:
STDMETHODIMP CDISControl::GetNets(VARIANT* nets)
{
SNet *netz;
int32_t num;
int result, i;
result = DIS_GetNetNum(securityHandle, &num);
netz = new SNet[num];
result = DIS_GetNet(securityHandle, netz, num); //getting some data
CComSafeArray<IDispatch*> netArray;
CComObject<CDISNet> *net;
CComVariant *var;
netArray.Create(num, 0);
for (i = 0;i<num;i++){
CComObject<CDISNet>::CreateInstance(&net);
if (net == NULL)
return S_FALSE;
net->AddRef();
net->Convert(netz[i]);
netArray[i] = net;
net->Release();
net = NULL;
}
CComVariant val(netArray.Detach());
val.Detach(nets);
delete [] netz;
netArray.Destroy();
return S_OK;
}
I instantiate CDISNet objects and put some data in them (Convert()). I put them in my safearray and release. As I understand it, the responsibility for destroying them is transferred to safearray. Afterwards, I box the array in a VARIANT so I can fill my [out, retval] parameter. Since it's an out parameter, the responsibility for destruction should be transferred to caller (in my case C#, i.e. its GarbageCollector). I dispose of my dynamic array 'netz' and I destroy safearray wrapper.
So what am I missing? What is left allocated? (This project is really making me appreciate all the comforts of .net).
Help. Please.
EDIT: Further debugging revealed to me that the problem is certainely in my CComObject objects. They aren't being deallocated. If I delete net;
in each iteration the array also looses data. I'm unsure as how to rectify that...
EDIT2:
Ok, I poked around this code for a bit, and the leak seems to go away when I comment out variant boxing. The problem is that I borrowed this piece of code from Visual Studio sample on safearrays. So, does anyone have any idea what's up with:
CComVariant val(netArray.Detach());
val.Detach(nets);
...and what to do about it?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
大多数(如果不是全部)ATL 的包装器都遵循 COM 约定——它们复制/addref 传入数据,因为它们的析构函数将销毁/释放。
因此,当您将分离的
SAFEARRAY
传递给CComVariant
的构造函数时,它将制作SAFEARRAY
的副本,这意味着没有人释放来自的结果CComSafeArray::Detach
。在这种情况下,我总是发现完全放弃返回值的包装器更容易;
另一种方法是将您的
CComSafeArray
直接传递给CComVariant
的构造函数,而不调用Detach
,但这会花费您额外的副本。我更喜欢上面介绍的原始访问,因为它是最直接且最便宜的。至于您的第一次编辑,您使用
AddRef/Release
所做的事情很好,尽管有些不必要。CComObject::CreateInstance
返回一个引用计数为 0 的对象,因此AddRef
会将其设置为 1,然后将其分配给CComSafeArray
将将其变为 2,然后将Release
返回到 1。除非
Convert
方法对对象的引用计数执行任何操作(例如QueryInterface
本身)或将其自身传递给另一个 COM 方法),您可以跳过AddRef/Release
对,并让Convert
在 refcount == 0 的情况下执行。然后将其添加到数组中会增加它,并且它会一直活着直到被释放。Most, if not all, of ATL's wrappers follow COM conventions -- they copy/addref incoming data, as their destructor will destroy/release.
So when you pass your detached
SAFEARRAY
toCComVariant
's constructor, it will make a copy of theSAFEARRAY
, which means nobody releases the result fromCComSafeArray::Detach
.In cases like this, I always found it easier to forego the wrapper for the return value entirely;
The alternative would be to pass your
CComSafeArray
directly toCComVariant
's constructor, without callingDetach
, but that would cost you an extra copy. I'd prefer the raw access presented above, as it is most straightforward and cheapest.As to your first edit, what you're doing with
AddRef/Release
is fine, if somewhat unnecessary.CComObject::CreateInstance
returns an object with reference count 0, so theAddRef
will bring it to 1, and then assigning it to theCComSafeArray
will bump it to 2, and the followingRelease
back down to 1.Unless the
Convert
method does anything with the object's reference count (e.g.QueryInterface
itself or pass itself to another COM method), you could skip theAddRef/Release
pair, and letConvert
execute with refcount == 0. Then adding it to the array would increase it, and it would stay alive until released.