[英]SWIG_csharp_string_callback leads to a memory leak when passing a string (const char*) from C++ to C#
使用 swig 控制器将字符串 (const char*) 参数从 C++ 传递到 C# 时,我面临内存泄漏。 我在 swig 论坛中发现了一个类似的问题,并提供了一些有价值的建议,但是,缺少正确的类型映射集(即使在当前的 swig 版本 2.0.11 中也未解决该问题)。
在花了几天的谷歌搜索和调查 swig 代码之后,我终于写了一组类型图,为我解决了这个问题。
我希望这个问题和发布的答案会有所帮助。
这是 char* 的一组类型映射,它为我完成了这项工作。
这个想法是将 char* 作为 IntPtr 传递给 C#,然后使用InteropServices.Marshal.StringToHGlobalAnsi()
将其转换为 C# 字符串(C# 字符串将被 GC 销毁)。 类似地,当将字符串从 C# 传递到 C++ 时,我们使用InteropServices.Marshal.StringToHGlobalAnsi()
方法将其转换为 IntPtr(并确保一旦调用返回,该对象将最终被销毁)。
// Alternative char * typemaps.
%pragma(csharp) imclasscode=%{
public class SWIGStringMarshal : IDisposable {
public readonly HandleRef swigCPtr;
public SWIGStringMarshal(string str) {
swigCPtr = new HandleRef(this, System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi(str));
}
public virtual void Dispose() {
System.Runtime.InteropServices.Marshal.FreeHGlobal(swigCPtr.Handle);
GC.SuppressFinalize(this);
}
~SWIGStringMarshal()
{
Dispose();
}
}
%}
%typemap(ctype) char* "char *"
%typemap(cstype) char* "string"
// Passing char* as an IntPtr to C# and then convert it to a C# string.
%typemap(imtype, out="IntPtr") char * "HandleRef"
%typemap(in) char* %{$1 = ($1_ltype)$input; %}
%typemap(out) char* %{$result = $1; %}
%typemap(csin) char* "new $imclassname.SWIGStringMarshal($csinput).swigCPtr"
%typemap(csout, excode=SWIGEXCODE) char *{
string ret = System.Runtime.InteropServices.Marshal.PtrToStringAnsi($imcall);$excode
return ret;
}
%typemap(csvarin, excode=SWIGEXCODE2) char * %{
set {
$imcall;$excode
} %}
%typemap(csvarout, excode=SWIGEXCODE2) char * %{
get {
string ret = System.Runtime.InteropServices.Marshal.PtrToStringAnsi($imcall);$excode
return ret;
} %}
%typemap(directorout) char* %{$result = ($1_ltype)$input; %}
%typemap(csdirectorout) char * "$cscall"
%typemap(directorin) char *
%{
$input = (char*)$1;
%}
%typemap(csdirectorin) char* "System.Runtime.InteropServices.Marshal.PtrToStringAnsi($iminput)";
附: 我在 Windows 32/64 以及 Linux 64 操作系统上测试了类型映射。
这是一个已知的错误,几年前就在 SWIG 组织中报告过,请参阅https://github.com/swig/swig/issues/283和https://github.com/swig/swig/issues/998
此错误在最新版本中尚未修复(直到现在是 4.0.2 版)。
代码的bug在swig\\Lib\\csharp\\std_string.i。 有了这个错误,当有字符串回调时,它会在我的应用程序中用 C++ 生成这样的函数:
void SwigDirector_MyCode::LogDebug(std::string const &message) {
char * jmessage = 0 ;
if (!swig_callbackLogDebug) {
throw Swig::DirectorPureVirtualException("SDS::ISDSLogger::LogDebug");
} else {
jmessage = SWIG_csharp_string_callback((&message)->c_str());
swig_callbackLogDebug(jmessage);
}
}
LogDebug 应从 C++ 代码调用。
在代码中,jmessage 是新分配的内存。 您可以验证它是否在调用后更改了 jmessage 中的第一个字符,并且您可以看到 (&message)->c_str() 没有更改。
有想法说应调用 free() 或 delete 来释放从 SWIG_csharp_string_callback() 创建的内存。
但这对我不起作用。 我正在使用 SWIG 从 C++ 生成 C# 包装器。
我的解决方案是在我的项目 .i 文件中为 std::string const & 类型定义新的类型映射。
%typemap(directorin) const std::string & %{ $input = const_cast<char *>($1.c_str()); %}
所以我生成的代码变成了:
void SwigDirector_MyCode::LogDebug(std::string const &message) {
char * jmessage = 0 ;
if (!swig_callbackLogDebug) {
throw Swig::DirectorPureVirtualException("MyCode::LogDebug");
} else {
jmessage = const_cast<char *>((&message)->c_str());
swig_callbackLogDebug(jmessage);
}
}
在这段代码中,不再分配 jmessage。
它已经在 Debian 9 mono 和 Windows 7 和 VS2017 上进行了测试。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.