C++/CLI - C# Interop - 字符串转换内存泄漏
C++/CLI - C# Interop - string conversion memory leak
我需要向本机代码公开一些现有的 .NET 逻辑(即程序集 MyManaged.dll),因此我决定创建 C++/CLI bridge。我创建了 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
转换中肯定存在泄漏。
我的问题很明显:如何在没有泄漏的情况下实现字符串转换。
谢谢!
您需要在使用后从 StringToHGlobalUni
释放指针。使用 Marshal.FreeHGlobal
或 LocalFree
.
虽然没有完全按照您的要求构建 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;
}
更新:请忽略反对者 - 正如您所看到的,他甚至拒绝解释这里的问题。不同的人有不同的动机...有一件事是肯定的:这里提供的解决方案完美无缺,没有任何内存泄漏。
我找到了解决方案(基于 Overview of Marshaling in C++ and 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*
也将被删除。这意味着您不应在使用后立即删除上下文,而是需要提供足够的时间来调用代码以实际获取结果字符串。
我需要向本机代码公开一些现有的 .NET 逻辑(即程序集 MyManaged.dll),因此我决定创建 C++/CLI bridge。我创建了 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
转换中肯定存在泄漏。
我的问题很明显:如何在没有泄漏的情况下实现字符串转换。
谢谢!
您需要在使用后从 StringToHGlobalUni
释放指针。使用 Marshal.FreeHGlobal
或 LocalFree
.
虽然没有完全按照您的要求构建 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;
}
更新:请忽略反对者 - 正如您所看到的,他甚至拒绝解释这里的问题。不同的人有不同的动机...有一件事是肯定的:这里提供的解决方案完美无缺,没有任何内存泄漏。
我找到了解决方案(基于 Overview of Marshaling in C++ and 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*
也将被删除。这意味着您不应在使用后立即删除上下文,而是需要提供足够的时间来调用代码以实际获取结果字符串。