使用COM互操作从C++到C#进行BSTRs编组

6
我是一个有用的助手,可以翻译文本。
我有一个用C++编写的进程外COM服务器,由一些C#客户端代码调用。服务器接口中的一个方法向客户端返回一个大型BSTR,我怀疑这会导致内存泄漏。代码可以运行,但我正在寻求有关编组BSTR的帮助。
简化一下,服务器方法的IDL为:
HRESULT ProcessRequest([in] BSTR request, [out] BSTR* pResponse);

实现看起来像:

HRESULT MyClass::ProcessRequest(BSTR request, BSTR* pResponse)
{
    USES_CONVERSION;
    char* pszRequest = OLE2A(request);
    char* pszResponse = BuildResponse(pszRequest);
    delete pszRequest;
    *pResponse = A2BSTR(pszResponse);
    delete pszResponse;
    return S_OK;
}

A2BSTR 在内部使用 SysAllocStringLen() 分配 BSTR。
在 C# 客户端中,我只需执行以下操作:
string request = "something";
string response = "";
myserver.ProcessRequest(request, out response);
DoSomething(response);

这个程序可以工作,因为请求字符串被发送到COM服务器并且正确的响应字符串被返回给C#客户端。但是每次与服务器的往返都会在服务器进程中泄漏内存。crt泄漏检测支持显示在crt堆上没有重大泄漏,因此我怀疑泄漏是使用IMalloc分配的。

我做错了什么吗?我发现模糊的评论说“所有输出参数必须使用CoTaskMemAlloc分配,否则互操作编组程序将不会释放它们”,但没有详细信息。

安迪


感谢这个问题和答案,因为我正在使用带有ATL COM对象和C ++的BSTR。我发现的一件事是,如果在IDL中将BSTR *指定为[out],那么如果传递的BSTR *已被初始化,则会出现内存泄漏。因此,您需要在IDL文件中将BSTR *声明为[in,out]。请参见http://msdn.microsoft.com/en-us/library/bdyd6xz6.aspx - Richard Chambers
3个回答

3
我看不出您的代码有明显的问题。建议您修改ProcessRequest方法以排除COM互操作作为泄漏源:
HRESULT MyClass::ProcessRequest(BSTR request, BSTR* pResponse)
{
    *psResponse = ::SysAllocStringLen(L"[suitably long string here]");
    return S_OK;
}

我怀疑不会泄漏,如果是这样的话,您已经将泄漏范围缩小到了您的代码。
我还要指出OLE2A在堆栈上分配内存,因此您不仅不应该删除pszRequest,而且也不应该使用OLE2A,因为存在堆栈溢出的可能性。请参见此文章以获取更安全的替代方案。
我建议您用::SysAllocString(CA2W(pszResponse))替换A2BSTR。

谢谢。看起来泄漏不在COM内存处理中,虽然我不确定在哪里。是时候尝试boundschecker了... - Andy Johnson

2

anelson已经讲得很清楚了,但我想补充几点:

  • CoTaskMemAlloc不是唯一的COM友好分配器--BSTR被默认的marshaller所识别,并将使用SysAllocString & friends进行释放/重新分配。

  • 为避免因堆栈溢出风险而避免使用USES_CONVERSION(请参见anelson的答案),您的完整代码应该像这样[1]

(请注意,A2BSTR是安全使用的,因为它在转换后调用SysAllocString,而不使用动态堆栈分配。此外,使用数组删除(delete[])作为BuildResponse可能会分配字符数组)

  • BSTR分配器具有缓存,这可能会使其看起来像存在内存泄漏。请参见http://support.microsoft.com/kb/139071以获取一些详细信息,或搜索OANOCACHE。您可以尝试禁用缓存并查看“泄漏”是否消失。

[1]

HRESULT MyClass::ProcessRequest(BSTR request, BSTR* pResponse)
{
    char* pszResponse = BuildResponse(CW2A(request));
    *pResponse = A2BSTR(pszResponse);
    delete[] pszResponse;
    return S_OK;
}

谢谢。我已经按照你建议的进行了更改。不过泄漏似乎在别处。 - Andy Johnson

-4

我猜你需要使用::SysFreeString()来销毁request。这段内存是在服务器端分配的。

此外,OLE2A可能会由于转换而分配内存(请查看)。你也不需要释放它。


谢谢回复。关于销毁“请求”,我认为输入参数是由调用者分配和释放的? - Andy Johnson
不,根据COM规则,被调用方从不释放传入参数。 - anelson
OLE2A使用_alloca,在堆栈上动态分配内存。如果字符串大小未知,则可能会很危险,但不需要解除分配--当函数结束时,堆栈空间将被回收。 - Kim Gräsman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接