将字符串 (const char*) 从 C++ 传递到 C# 时,SWIG_csharp_string_callback 会导致内存泄漏

Posted

技术标签:

【中文标题】将字符串 (const char*) 从 C++ 传递到 C# 时,SWIG_csharp_string_callback 会导致内存泄漏【英文标题】:SWIG_csharp_string_callback leads to a memory leak when passing a string (const char*) from C++ to C# 【发布时间】:2014-01-08 11:24:56 【问题描述】:

使用 swig 控制器将字符串 (const char*) 参数从 C++ 传递到 C# 时,我遇到了内存泄漏。我在 swig 论坛中发现了一个类似的 question 并提供了一些有价值的建议,但是,缺少正确的类型映射集(即使在当前的 swig 版本 2.0.11 中也没有解决该问题)。

经过几天的谷歌搜索和研究 swig 代码后,我终于编写了一组类型图,为我解决了这个问题。

我希望这个问题和发布的答案会有所帮助。

【问题讨论】:

我很困惑为什么会发生这种情况,我在一个大型应用程序中使用 swig 在 C# 和 C++ 之间传递了字符串,并且从未观察到内存泄漏(在某些时候有一些泄漏但不是由于对此)。听起来您找到了解决方案,这很好。 引用swig forum:如果 C# 回调将托管字符串返回给 C(这发生在 SWIG_csharp_string_callback 方法中),似乎是.Net 或 Mono 会分配一个非托管字符串并将其返回给本机代码,因为否则返回值 - 如果它实际上指向托管对象,则需要固定,但这不会发生。因此,这个新分配的内存没有被清理,这可能会导致内存泄漏。 【参考方案1】:

这里是 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)";

ps。我在 Windows 32/64 以及 Linux 64 操作系统上测试了类型映射。

【讨论】:

【参考方案2】:

这是一个已知错误,多年前已在 SWIG org 中报告过,请参阅 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);
  

应从 C++ 代码调用 LogDebug。

在代码中,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 上进行了测试。

【讨论】:

以上是关于将字符串 (const char*) 从 C++ 传递到 C# 时,SWIG_csharp_string_callback 会导致内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

c++ uint32 如何转 const char

C++ 我将一个const char*的字符串转换为string出错

C++ 将 char 转换为 const char*

C++ 将字符串添加到 const char* 数组

C++ 从 'char' 到 'const char' 的无效转换

从 const char* 复制到字节数组 C++/c# interop Marshal::Copy