ATL C++ ccomobjects 的 safearray 造成内存泄漏

发布于 2024-12-10 02:53:08 字数 1850 浏览 0 评论 0原文

我发现自己需要帮助。现在,我对 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

风和你 2024-12-17 02:53:08

大多数(如果不是全部)ATL 的包装器都遵循 COM 约定——它们复制/addref 传入数据,因为它们的析构函数将销毁/释放。

因此,当您将分离的 SAFEARRAY 传递给 CComVariant 的构造函数时,它将制作 SAFEARRAY 的副本,这意味着没有人释放来自的结果CComSafeArray::Detach

在这种情况下,我总是发现完全放弃返回值的包装器更容易;

nets->vt = VT_ARRAY | VT_DISPATCH;
nets->parray = netArray.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 to CComVariant's constructor, it will make a copy of the SAFEARRAY, which means nobody releases the result from CComSafeArray::Detach.

In cases like this, I always found it easier to forego the wrapper for the return value entirely;

nets->vt = VT_ARRAY | VT_DISPATCH;
nets->parray = netArray.Detach();

The alternative would be to pass your CComSafeArray directly to CComVariant's constructor, without calling Detach, 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 the AddRef will bring it to 1, and then assigning it to the CComSafeArray will bump it to 2, and the following Release 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 the AddRef/Release pair, and let Convert execute with refcount == 0. Then adding it to the array would increase it, and it would stay alive until released.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文