none
アンマネージDLLへの参照型(class)の参照渡し(ref)時のメモリ解放 RRS feed

  • 質問

  • アンマネージDLLへ参照型(class)のオブジェクトを参照渡し(ref)し、DLL側でメモリをアロケートした場合、

    そのメモリはDLL側で解放する必要があるのでしょうか?それともC#側で勝手に解放されるのでしょうか?

    // アンマネージDLL側
    void Hoge(char** pStr)
    {
      (*pStr) = new char[5];
      strcpy(*pStr, "test");
    }
    
    // C#側
    [DllImport("Test.dll")]
    public static extern void Hoge(out string s);
    
    public void Test()
    {
      string s;
      Hoge(s);
      Console.WriteLine(s);
    }
    

    2011年3月3日 11:13

回答

  • なにせ自動的に解放しようにも、解放する手段は色々あってどれを選べばいいか、シグネチャだけでは決定できません。

    UnmanagedType.BStr の場合は SysFreeString を使って解放を試みます。それ以外の場合は CoTaskMemFree だったかな? いずれにせよ C++ の new で確保された領域を開放することはできません。

    誤った解放を避けるためにも、DLL 側でメモリが確保される場合は(COM の BSTR を除けば)やってはいけません。つまり、IntPtr で受け取って、必要に応じて Marshal.PtrToStringAuto とか使って変換するようにしましょう。

    もちろん、解放処理の口は C++ 側に用意しなければなりません。

    • 回答としてマーク Mito Memel 2011年3月3日 14:33
    2011年3月3日 13:04

すべての返信

  • C#ではなくC++の方が適切だと思いますが、new[]演算子で確保したメモリをdelete[]演算子で解放すると幸せになれるそうです。
    2011年3月3日 12:24
  • つまり、アンマネージDLL側でメモリを解放する必要があるということでしょうか?

    アンマネージDLL側からreturnでchar*を返した場合、そのポインタはランタイムが自動で解放するという記述はMSDNにあったのですが、

    引数で渡されたポインタに対してnewした場合、そのポインタはマネージとアンマネージのどちら側の所有になるのかの記述が見つからず困っていました。

    よろしければ、その記述がある場所がありましたら教えていただけないでしょうか?

    2011年3月3日 12:37
  • なにせ自動的に解放しようにも、解放する手段は色々あってどれを選べばいいか、シグネチャだけでは決定できません。

    UnmanagedType.BStr の場合は SysFreeString を使って解放を試みます。それ以外の場合は CoTaskMemFree だったかな? いずれにせよ C++ の new で確保された領域を開放することはできません。

    誤った解放を避けるためにも、DLL 側でメモリが確保される場合は(COM の BSTR を除けば)やってはいけません。つまり、IntPtr で受け取って、必要に応じて Marshal.PtrToStringAuto とか使って変換するようにしましょう。

    もちろん、解放処理の口は C++ 側に用意しなければなりません。

    • 回答としてマーク Mito Memel 2011年3月3日 14:33
    2011年3月3日 13:04
  • 所有という概念はありません。純粋にC++のdelete[]演算子で解放する必要がある、それだけです。C#ではC++のdelete[]演算子を呼び出しようがないので、C++側に別途メモリ解放関数を用意し、C#からそれを呼び出すほかありません。
    2011年3月3日 13:09
  • なるほど...

    つまり、最初のコード自体が間違っており、

    このような方法でstringを渡すと、C++側で解放してもランタイムがCoTaskMemFreeで2重に解放しようとして危険、ということですね。

    ありがとうございます。

    2011年3月3日 13:21
  • 2重解放が危険なのではありません。

    もう3度目になりますが、確保した時と対になる解放方法を選択する必要があり、new[]演算子に対しては、delete[]演算子を使う必要があります。

    2011年3月3日 22:32
  • ・最初のコードの問題は、「new[]で確保したメモリがCoTaskMemFreeで解放されてしまう」ということである

    ・最初のコードのC++側にdelete[]でメモリを解放する関数を追加したとしても、Hoge()でポインタを渡した時点でCoTaskMemFreeされてしまうので、そもそもHoge(out string s)という方法で文字列を渡してはいけない。IntPtrで渡してMarshal.PtrToStringAutoで変換するなどの方法で文字列は渡すべき

    ということですね。
    ありがとうございます。

    2011年3月3日 23:16
  • たとえば、C++側で

    void DeleteCharArray(char* p)
    {
      delete [] p;
    }
    

    という関数を定義し、これをカスタムマーシャラーのCleanUpNativeData()で呼んでやれば、C#側の宣言がvoid Hoge(ref string s)でも正しく解放できるでしょうか?

    それとも、カスタムマーシャラーを使ってもCoTaskMemFreeは自動で呼ばれてしまうのでしょうか?

    2011年3月4日 4:19