简体   繁体   English

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

[英]SWIG_csharp_string_callback leads to a memory leak when passing a string (const char*) from C++ to C#

I'm facing a memory leak when passing string (const char*) arguments from C++ to C# using swig directors.使用 swig 控制器将字符串 (const char*) 参数从 C++ 传递到 C# 时,我面临内存泄漏。 I found a similar question in swig forums with some valuable advices, however, the set of correct typemaps were missing (the issue is unresolved even in the current swig version 2.0.11).我在 swig 论坛中发现了一个类似的问题,并提供了一些有价值的建议,但是,缺少正确的类型映射集(即使在当前的 swig 版本 2.0.11 中也未解决该问题)。

After spending several days of googling and investigating the swig code I finally wrote a set of typemaps which fixed the problem for me.在花了几天的谷歌搜索和调查 swig 代码之后,我终于写了一组类型图,为我解决了这个问题。

I hope this question and posted answer will be helpful.我希望这个问题和发布的答案会有所帮助。

Here are the set of typemaps for char* which did the job for me.这是 char* 的一组类型映射,它为我完成了这项工作。

The idea is to pass char* as an IntPtr to C# then convert it into a C# string using InteropServices.Marshal.StringToHGlobalAnsi() (The C# string will be destroyed by GC).这个想法是将 char* 作为 IntPtr 传递给 C#,然后使用InteropServices.Marshal.StringToHGlobalAnsi()将其转换为 C# 字符串(C# 字符串将被 GC 销毁)。 Similarly, when passing a string from C# to C++ we convert it into an IntPtr with InteropServices.Marshal.StringToHGlobalAnsi() method (and ensure that the object will be finally destroyed once the call returns).类似地,当将字符串从 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.附: I tested the typemaps on Windows 32/64 as well as on Linux 64 operating systems.我在 Windows 32/64 以及 Linux 64 操作系统上测试了类型映射。

This is a known bug, an it has been reported years ago in SWIG org, see https://github.com/swig/swig/issues/283 and https://github.com/swig/swig/issues/998这是一个已知的错误,几年前就在 SWIG 组织中报告过,请参阅https://github.com/swig/swig/issues/283https://github.com/swig/swig/issues/998

This bug has not been fixed in latest version (until now is version 4.0.2).此错误在最新版本中尚未修复(直到现在是 4.0.2 版)。

The bug of the code is in swig\\Lib\\csharp\\std_string.i.代码的bug在swig\\Lib\\csharp\\std_string.i。 With the bug, when there is callback with string, it generates function like this in C++ in my application:有了这个错误,当有字符串回调时,它会在我的应用程序中用 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);
  }
}

The LogDebug shall be called from C++ code. LogDebug 应从 C++ 代码调用。

In the code, the jmessage is new allocated memory.在代码中,jmessage 是新分配的内存。 You can verify it you change the first char in the jmessage after the call, and you can see the (&message)->c_str() is not changed.您可以验证它是否在调用后更改了 jmessage 中的第一个字符,并且您可以看到 (&message)->c_str() 没有更改。

There are ideas saying that the free() or delete shall be called to deallocate the memory created from SWIG_csharp_string_callback().有想法说应调用 free() 或 delete 来释放从 SWIG_csharp_string_callback() 创建的内存。

But this is not working for me.但这对我不起作用。 I am using SWIG to generate C# wrapper from C++.我正在使用 SWIG 从 C++ 生成 C# 包装器。

My solution is to define new typemap for the type std::string const & in my project .i file.我的解决方案是在我的项目 .i 文件中为 std::string const & 类型定义新的类型映射。

%typemap(directorin) const std::string & %{ $input = const_cast<char *>($1.c_str()); %}

So my generated code becomes:所以我生成的代码变成了:

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);
  }
}

In this code, jmessage is not allocated anymore.在这段代码中,不再分配 jmessage。

It has been tested on Debian 9 mono and Windows 7 with VS2017.它已经在 Debian 9 mono 和 Windows 7 和 VS2017 上进行了测试。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM