簡體   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#

使用 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/283https://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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM