【问题标题】:C++/CLI - C# Interop - string conversion memory leakC++/CLI - C# Interop - 字符串转换内存泄漏
【发布时间】:2016-08-12 18:10:05
【问题描述】:

我需要将一些现有的 .NET 逻辑(即程序集 MyManaged.dll)暴露给本机代码,因此我决定创建 C++/CLIbridge。我创建了 C++/CLI 项目并添加了对 MyManaged.dll 的引用。长话短说——它有效——我已经成功地访问了所有应该可以从本机代码访问的东西。

但是大问题是我的实现泄漏了内存。经过几天的测试和研究,我将问题缩小到System::String const wchar_t 转换。最后,我创建了一个简单的 C++/CLI 项目来演示(重现)该问题:

#define EXPORTED __declspec(dllexport)

System::String^ ToManaged(const wchar_t* unmanagedString)
{
    return gcnew System::String(unmanagedString);
}

const wchar_t* ToUnmanaged(System::String^ managedString)
{
    return (wchar_t*) System::Runtime::InteropServices::Marshal::StringToHGlobalUni(managedString).ToPointer();
}

EXPORTED const wchar_t* __stdcall GetString(const wchar_t* dummy)
{
    return ToUnmanaged(ToManaged(dummy));
}

(如果从前面的代码中看不出来的话——我是 C++/CLI 的新手)

正如我所提到的,代码有效但会累积内存消耗,因此System::String const wchar_t 转换肯定存在泄漏。

我的问题很明显:如何实现字符串转换没有泄漏。

谢谢!

【问题讨论】:

  • 我会使用 COM 并且 COM 启用您需要访问的方法。使用 tlb.exe 生成 COM 互操作。少了很多头痛。我沿着 cli/c++ 路径走下去,发现了 dll 加载顺序问题和病毒软件创建问题等问题。
  • @CKIsLearning - 感谢您提供帮助。老实说,出于某种原因,我非常反对 COM。如果我没有找到任何其他解决方案,我会去那里,但同时我已经解决了这个问题,没有理由这样做。还是谢谢!
  • 对 Marshal::StringToHGlobalUni() 的每次调用必须与对 Marshal::FreeCoTaskMem() 的调用配对。这会使您的 ToUnmanaged() 函数损坏而没有希望进行简单的修复,该调用不会发生。您将不得不重新考虑这一点,考虑使用更智能的字符串类型,例如 std::wstring
  • @HansPassant - 谢谢,但正如您在下面看到的,我已经使用 <msclr\marshal.h> 解决了这个问题。

标签: c# c++-cli interop


【解决方案1】:

更新:请忽略负面选民 - 正如你所看到的,他甚至拒绝解释这里出了什么问题。不同的人有不同的动机......有一件事是肯定的:这里提供的解决方案完美无缺,没有任何内存泄漏。

我找到了解决方案(基于Overview of Marshaling in C++marshal_context::marshal_as)。所以应该改变以下内容:

#include <msclr\marshal.h>

System::String^ ToManaged(const wchar_t* unmanagedString)
{
    return msclr::interop::marshal_as<System::String^>(unmanagedString);
}

gcroot<msclr::interop::marshal_context^> context;

const wchar_t* ToUnmanaged(System::String^ managedString)
{        
    msclr::interop::marshal_context^ unpacked = context;

    if (unpacked != nullptr)
        delete unpacked;

    context = gcnew msclr::interop::marshal_context();

    return context->marshal_as<const wchar_t*>(managedString);
}

注意:这里我对marshal_context 实例进行了非常笨拙的处理——当下一个调用到达时,前一个调用的结果被删除。此实现在多线程场景中会崩溃,因此您应该实现一个更好的实现,同时牢记以下几点:

  • marshal_context 实例可用于多次调用,但应不时将其删除(以从先前编组的字符串中释放内存);
  • 一旦marshal_context 被删除 - 所有使用它创建的const wchar_t* 也会被删除。这意味着您不应该在使用后立即删除上下文,但您需要为调用代码提供足够的时间才能真正获取结果字符串。

【讨论】:

  • 如果对此投反对票的人可以提供一些解释,那就太好了。我可以确认提供的解决方案就像魅力一样,没有任何泄漏。
  • 我猜你是因为脆弱的marshal_context 处理而赢得了反对票,所以“像魅力一样工作” 是相当相对的......
  • @LucasTrzesniewski - 答案的重点不是复制粘贴 - 使用相同的确切代码示例,而是应该使用的方法 - &lt;msclr\marshal.h&gt;。而且这种方法确实像魅力一样工作(我已经在生产中使用它,每秒调用 20,000 次,没有任何内存泄漏)。关于代码示例 - 我已经在答案中说明了所有内容,并提供了一些关于如何正确实现它的提示。
  • 但是,归根结底,这不再重要了。我在这里的目的不是收集积分(显然),而是在需要时获得帮助,并与他人分享我学到的东西。反对票不会对我造成伤害,但会伤害未来的访问者,他们可能会因为错误的反对票而跳过完全有效的答案。
【解决方案2】:

您需要在使用后将指针从StringToHGlobalUni 中释放出来。使用Marshal.FreeHGlobalLocalFree

【讨论】:

  • 感谢您的帮助!但这没有帮助(我已经尝试过)。
  • 更准确地说:在我之前解决问题的尝试之一中,我实现了一个逻辑,在一段时间后释放所有以这种方式创建的IntPtr,但它没有帮助。
  • 或者更准确地说:这可能是一个问题,但肯定不是唯一的问题。
【解决方案3】:

虽然不是根据您的确切 api 构建的,但我认为这解决了内存问题

HRESULT GetString(BSTR* p_bstrResult, unsigned long* ulErrCode)
{
    HRESULT hr = S_OK;

    try
    {
        System::String ^systemstring = gcnew System::String("");
                    DotNetObject::o = gcnew  DotNetObject:: DotNetObjectComponent();
        *ulErrCode = (unsigned long)o->GetString(systemstring); 
        pin_ptr<const wchar_t> wch = PtrToStringChars(systemstring);
        _bstr_t bstrt(wch);
        *p_bstrResult = bstrt.GetBSTR(); // native client babysits
        delete systemstring;        
    }
    catch(Exception ^ ex)
    {

    }   
    return hr;

}

【讨论】:

  • 感谢您的帮助!同时,我找到了解决方案(正如您在我的回答中看到的那样)。我不确定您的解决方案是否也有效,但它看起来比我找到的解决方案更复杂。还是谢谢!
猜你喜欢
  • 2011-05-04
  • 1970-01-01
  • 2015-06-12
  • 2017-06-19
  • 1970-01-01
  • 1970-01-01
  • 2011-05-14
  • 2014-04-30
  • 1970-01-01
相关资源
最近更新 更多