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

时间:2014-01-08 11:24:56

标签: c# c++ memory-leaks swig

使用swig director将字符串(const char *)参数从C ++传递给C#时,我面临内存泄漏。我在swig论坛中发现了类似question的一些有价值的建议,然而,缺少一组正确的类型图(即使在当前的swig版本2.0.11中,该问题仍未得到解决)。

经过几天的谷歌搜索和调查swig代码后,我终于编写了一组解决问题的字体图。

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

2 个答案:

答案 0 :(得分:2)

以下是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操作系统上测试了这些类型图。

答案 1 :(得分:0)

这是一个已知的错误,几年前在SWIG org中已有报道,请参见 https://github.com/swig/swig/issues/283https://github.com/swig/swig/issues/998

此错误尚未在最新版本中修复(直到现在是4.0.2版)。

该代码的错误在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。

%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。

它已在VS2017的Debian 9 mono和Windows 7上进行了测试。