【问题标题】:Marshalling BSTRs from C++ to C# with COM interop使用 COM 互操作将 BSTR 从 C++ 编组到 C#
【发布时间】:2010-11-20 23:17:10
【问题描述】:

我有一个用 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# 客户端。但是每次到服务器的往返都会在 server 进程中泄漏内存。 crt 泄漏检测支持显示 crt 堆上没有明显的泄漏,所以我怀疑泄漏是用 IMalloc 分配的。

我在这里做错了吗?我发现模糊的 cmets 说“必须使用 CoTaskMemAlloc 分配所有参数,否则互操作编组器不会释放它们”但没有详细信息。

安迪

【问题讨论】:

  • 感谢这个问题和答案,因为我将 BSTR 与 ATL COM 对象和 C++ 一起使用。我发现的一件事是,如果您在 IDL 中将 BSTR * 指定为 [out],那么如果传递的 BSTR * 已被初始化,您将获得内存泄漏。因此,您需要在 IDL 文件中将 BSTR * 声明为 [in,out]。见msdn.microsoft.com/en-us/library/bdyd6xz6.aspx

标签: c# c++ com interop atl


【解决方案1】:

我认为您的代码没有明显问题。建议您修改 ProcessRequest 方法以排除 COM 互操作作为泄漏源:

HRESULT MyClass::ProcessRequest(BSTR request, BSTR* pResponse)
{
    *psResponse = ::SysAllocStringLen(L"[suitably long string here]");
    return S_OK;
}

我怀疑这不会泄漏,在这种情况下,您已将泄漏范围缩小到您的代码。

我还要注意 OLE2A 在堆栈上分配内存,因此您不仅不应该删除 pszRequest,而且根本不应该使用 OLE2A,因为堆栈溢出的可能性。请参阅this article 了解更安全的替代方案。

我还建议您将 A2BSTR 替换为 ::SysAllocString(CA2W(pszResponse))

【讨论】:

  • 谢谢。看起来泄漏不在 COM 内存处理中,尽管我不确定在哪里。是时候尝试边界检查器了...
【解决方案2】:

anelson 已经很好地介绍了这一点,但我想补充几点;

  • CoTaskMemAlloc 不是唯一对 COM 友好的分配器 - BSTR 被默认编组器识别,并将使用 SysAllocString 和朋友来释放/重新分配。

  • 避免使用 USES_CONVERSION(由于堆栈溢出风险——请参阅 anelson 的回答),您的完整代码应该是这样的 [1]

(请注意,A2BSTR 可以安全使用,因为它在转换后调用 SysAllocString,并且不使用动态堆栈分配。另外,使用 array-delete (delete[]),因为 BuildResponse 可能会分配一个字符数组)

  • BSTR 分配器有一个缓存,可以使它看起来好像存在内存泄漏。有关详细信息,请参阅http://support.microsoft.com/kb/139071,或查看 OANOCACHE 的 Google。您可以尝试禁用缓存并查看“泄漏”是否消失。

[1]

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

【讨论】:

  • 谢谢。我已经按照您的建议进行了更改。看起来泄漏是其他的。
【解决方案3】:

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

另外,OLE2A 可能会因转换而分配内存(看看)。你也不释放它。

【讨论】:

  • 感谢您的回复。重新销毁“请求”,我认为 [in] 参数是由调用者分配和释放的?
  • 不,根据 COM 规则,被调用者永远不会释放 [in] 参数。
  • OLE2A 使用 _alloca,它在堆栈上动态分配内存。如果字符串的大小未知,但不需要解除分配,这可能很危险——函数结束时会回收堆栈空间。
猜你喜欢
  • 2011-10-25
  • 2011-06-28
  • 1970-01-01
  • 2010-12-14
  • 2014-12-17
  • 2010-11-02
  • 1970-01-01
  • 2010-10-02
  • 1970-01-01
相关资源
最近更新 更多