7

.NET アプリケーションは C dll を呼び出します。C コードは、char 配列にメモリを割り当て、この配列を結果として返します。.NET アプリケーションは、この結果を文字列として取得します。

C コード:

extern "C" __declspec(dllexport) char* __cdecl Run()
{
    char* result = (char*)malloc(100 * sizeof(char));
    // fill the array with data
    return result;
}

C# コード:

[DllImport("Unmanaged.dll")]
private static extern string Run();

...
string result = Run();
// do something useful with the result and than leave it out of scope

いくつかのテストでは、ガベージ コレクターが C コードによって割り当てられたメモリを解放しないことが示されています。

どんな助けでも大歓迎です。:)

4

7 に答える 7

7

P/Invoke マーシャラーは、戻り値の型のメモリが CoTaskMemAlloc() で割り当てられたと想定し、CoTaskMemFree() を呼び出して解放します。これを行わないと、Vista と Win7 では例外が発生してプログラムが失敗しますが、XP では暗黙のうちにメモリ リークが発生します。SysAllocString() を使用して機能させることはできますが、[DllImport] 属性で戻り値の型に注釈を付ける必要があります。そうしないと、Win7 での診断がなくてもリークが発生します。BSTR は、CoTaskMemAlloc によって割り当てられたメモリ ブロックへのポインターではありません。指定されたアドレスの前に、文字列サイズを格納する 4 バイトがあります。

次の組み合わせのいずれかが適切に機能します。

extern "C" __declspec(dllexport)
BSTR __stdcall ReturnsAString() {
  return SysAllocString(L"Hello world");
}

[DllImport(@"c:\projects\cpptemp1\debug\cpptemp1.dll")]
[return: MarshalAs(UnmanagedType.BStr)]   // NOTE: required!
private static extern string ReturnsAString();

または:

extern "C" __declspec(dllexport)
const wchar_t* __stdcall ReturnsAString() {
  const wchar_t* str = L"Hello world";
  wchar_t* retval = (wchar_t*)CoTaskMemAlloc((wcslen(str)+1) * sizeof(wchar_t));
  wcscpy(retval, str);
  return retval;
}

[DllImport(@"c:\projects\cpptemp1\debug\cpptemp1.dll", CharSet=CharSet.Auto)]
private static extern string ReturnsAString();

メモリ管理の問題が発生しないように、クライアント コードがバッファを渡すことを検討する必要があります。これは次のようになります。

extern "C" __declspec(dllexport)
void __stdcall ReturnsAString(wchar_t* buffer, size_t buflen) {
  wcscpy_s(buffer, buflen, L"Hello world");
}

[DllImport(@"c:\projects\cpptemp1\debug\cpptemp1.dll", CharSet=CharSet.Auto)]
private static extern void ReturnsAString(StringBuilder buffer, int buflen);
...
    StringBuilder sb = new StringBuilder(256);
    ReturnsAString(sb, sb.Capacity);
    string s = sb.ToString();
于 2009-12-19T13:47:59.253 に答える
7

管理文字列は char* と同じではありません。秘密裏に何が起こるかというと、相互運用層のマーシャリング コードがアンマネージ文字列のコピーを作成してマネージ文字列に変換しますが、メモリがどのように割り当てられたかがわからないため、そのメモリを解放できません。

ただし、char* の代わりに BSTR を割り当てて返すこともできます。相互運用層は、従来の管理されていないデータ型よりも自動化データ型をうまく処理します。

それが重要な理由は、char* と BSTR がメモリに割り当てられる方法です。

char* バッファーは、CLR が何も知らないプライベートな割り当て/割り当て解除ルーチンを使用して C++ ランタイムのヒープに割り当てられるため、そのメモリを削除する方法はありません。さらに悪いことに、char* が指すバッファーは、dll コードの内部ヒープ実装によって割り当てられるか、プライベート クラスのメンバー変数を指すことさえあります。

一方、BSTR は Windows API SysAllocString を使用して割り当てられ、SyFreeStirng によって解放されます。CLR 相互運用レイヤーはこれらの Windows API を認識しているため、アンマネージ コードから取得した BSTR を解放する方法を認識しています。

于 2009-12-19T08:59:27.553 に答える
6

マネージ コードからアンマネージ メモリを解放することはできません。free関数によって返されたポインターを呼び出すルーチンを C で記述しRun、.NET から P/Invoke する必要があります。

もう 1 つのオプションは、.NET でアンマネージ メモリを割り当て、ポインターを C 関数に渡してデータを入力し、最後にこのポインターを解放することです。

IntPtr ptr = Marshal.AllocHGlobal(100 * sizeof(char));
SomeUnmanagedFunc(ptr);
Marshal.FreeHGlobal(ptr);
于 2009-12-19T08:37:07.990 に答える
3

これを行う別の方法は、マネージド文字列 (StringBuilder インスタンス) を P/Invoke を介して (Run関数へのパラメーターとして) 渡すことです。

そうすれば、管理されていない側では割り当てが行われません。

つまり、次のようなものになります。

extern "C" __declspec(dllexport) void __cdecl Run(char* data)
{
    // fill the array with data
    // no return value (void)
}

次のように呼び出します。

[DllImport("Unmanaged.dll", CharSet = CharSet.Ansi)]
static extern void Run(StringBuilder result);

StringBuilder result = new StringBuilder(100);
Run(result);
于 2009-12-19T08:42:22.500 に答える
2

PInvokeに関するいくつかの質問を読んでいて、ここで停止しました。問題がまだあなたに関係しているかどうかはわかりませんが、将来の読者に私の答えを投稿することにしました。

ダリン・ディミトロフの答えに対するあなたの最後のコメントについてです。割り当てられたメモリのサイズがわからない場合の典型的な解決策は、nullポインタを使用してアンマネージ関数を呼び出し、outパラメータでサイズを受け取ることです。次に、必要なスペースを割り当てて、アンマネージ関数を再度呼び出します。

以下の例:

//MANAGED SIDE  
IntPtr ptr = IntPtr.Zero;  
int size = 0;  
myFunc(ptr, out size);  
ptr = Marshal.AllocHGlobal(size);  
myFunc(ptr, out size);  
//Do your work..  
Marshal.FreeHGlobal(ptr);  



//UNMANEGED SIDE  
int myFunc(void* dest, size_t& size){  
   if(dest==NULL)  
       //calculate de size..  
       size = 'resul'  
       return 0;  
    }  
    // create the array and copy all elements   
    memcopy(dest, ... , size);  
    //free all allocated space in unmanaged and return success  
    return 0;  
}  
于 2010-02-26T14:17:14.267 に答える
-1

.NET メモリは、GC によってクリアされるように CLR 内に割り当てる必要があります。C DLL 内でブロックを解放する関数を追加する必要があります。

メモリを作成した C DLL の同じインスタンス内でメモリを解放することを忘れないでください。混ぜ合わせることはできません。

于 2009-12-19T08:38:52.680 に答える