0

C++ で書かれたアンマネージ ライブラリのラッパーを書こうとしています。DllImportライブラリのコーダーは、次のような sを含む C# ラッパーの例を教えてくれました。

[DllImport(LibraryPath, CallingConvention = CallingConvention.Cdecl)]
internal static extern int doSomething(int inputArraySize, byte* inputArrayPointer, out int outputInteger, out int outputArraySize, out float* outputArrayPointer);

public static int DoSomething(byte[] inputArray, out int outputInteger, out float[] outputArray)
{
    int arraySize = inputArray.Length;

    int outputArraySize;
    float* outputArrayPointer;
    outputArray = null;

    fixed (byte* arrayPointer = inputArray)
    {
        int result = doSomething(arraySize, arrayPointer, out outputInteger, out outputArraySize, out outputArrayPointer);

        if (result == 0)
        {
            outputArray = new float[outputArraySize];

            for (int i = 0; i < outputArraySize; i++)
            {
                outputArray[i] = outputArrayPointer[i];
            }

            // This is another imported function.
            freePointer(outputArrayPointer);
        }
    }

    return result;
}

彼はコードで を使用していますが、安全でないコードとポインターをすべてfixed使用して失うだけでよいのではないかと考えています。ref次のように書き換えることができます。

[DllImport(LibraryPath, CallingConvention = CallingConvention.Cdecl)]
internal static extern int doSomething(int arraySize, ref byte[] arrayPointer, out int someInteger, out int outputArraySize, out float[] someArrayPointer);

public static int DoSomething(byte[] inputArray, out int outputInteger, out float[] outputArray)
{
    int outputArraySize;

    int result = doSomething(inputArray.Length, ref inputArray, out outputInteger, out outputArraySize, out outputArray);

    return result;
}

上記のコードは、ポインターを使用した最初のコードと意味が同じですか?

  1. 私のバージョンを使用しても大丈夫ですか?
  2. 私のバージョンを使用することの欠点はありますか?
  3. を使用する場合ref、管理されていない libraray 関数に渡された配列は、関数の実行中ずっと安全ですか?
  4. そして、その機能は何をしていると思いますfreePointer()か? とにかく、ポインタはガベージコレクションによって収集されます。なぜそのような機能が必要なのですか?

この問題に関する提案をいただければ幸いです。

編集:追加のコードなしでは、C++ ポインターを C# 配列として取得することはできないと思います。最善の方法は何ですか?安全でないコードやポインターを使用せずにこれらすべてを行う方法はありますか?

編集 2:freePointer()この関数は、アンマネージ コードによって割り当てられた配列を解放すると思います。しかし、配列サイズをパラメーターとして取得せずに、ポインターだけで配列を解放するにはどうすればよいでしょうか?

4

1 に答える 1

2

あなたのコード (いくつかの小さな変更を加えたもの) は、マネージ コードからネイティブ コードに配列へのポインターを渡すために機能します。ネイティブ コードはマネージ配列 ( float[]) を割り当てる方法を知らないため、マネージ側でそれを 1 つとして扱うことは期待できないため、逆方向には機能しません。

ただし、生のfloat *バックを渡す代わりに、を返すように入力し、データを抽出するためにIntPtr使用できます。Marshal.Copy

これをまとめると、新しい P/Invoke 署名とコードは次のようになります。

[DllImport(...)]
internal static extern int doSomething(int arraySize, byte[] arrayPointer,
    out int outputInteger, out int outputArraySize,
    out IntPtr outputArrayPointer);

[DllImport(...)]
internal static extern void freePointer(IntPtr pointer);

public static int DoSomething(byte[] inputArray, out int outputInteger, out float[] outputArray)
{
    outputArray = null;
    int outputArraySize;
    IntPtr outputArrayPointer;

    int result = doSomething(inputArray.Length, inputArray, out outputInteger, out outputArraySize, out outputArrayPointer);

    if (result == 0)
    {
        outputArray = new float[outputArraySize];
        Marshal.Copy(outputArrayPointer, outputArray, 0, outputArraySize);

        freePointer(outputArrayPointer);
    }

    return result;
}

P/Invoke 署名が配列を受け取るように記述されている場合、マーシャリング レイヤーは自動的に配列をメモリに固定し (そのため、解放または移動できず、ネイティブ コードに安全に渡すことができます)、次にポインターを配列の最初の要素をネイティブ コードに渡します。対照的にref inputArray、マネージ配列オブジェクト (メモリ内の別の場所) へのポインターを渡します。

これらの変更によりfixed (byte* arrayPointer、呼び出しコードから を削除できるようにIntPtrなり、ネイティブ コードに を使用してデータを返すことができます。ネイティブ コードがメモリを割り当てたため、.NET ガベージ コレクターはそれを解放する方法を認識していないためfreePointer、データをコピーした後に呼び出すことが非常に重要です。メモリを割り当てた割り当てルーチンは、割り当てられたブロックのサイズをどこかに (多くの場合、割り当てられたメモリの前のヘッダー ブロックに) 格納しているため、解放するデータの量がわかります。これは、配列サイズを に渡す必要がないことを意味しますfreePointer

于 2013-02-09T21:36:48.203 に答える