2

ネイティブ dll が CoTaskMemAlloc を介して割り当てたデータを C# アプリケーションにマーシャリングしようとしていますが、その方法が単純に間違っているのか、メソッド C# 側の微妙な装飾が欠けているのか疑問に思っています。

現在、私はc ++側を持っています。

extern "C" __declspec(dllexport) bool __stdcall CompressData(  unsigned char* pInputData, unsigned int inSize, unsigned char*& pOutputBuffer, unsigned int& uOutputSize)
{ ...
    pOutputBuffer = static_cast<unsigned char*>(CoTaskMemAlloc(60000));
    uOutputSize = 60000;

そしてC#側。

    private const string dllName = "TestDll.dll";

    [System.Security.SuppressUnmanagedCodeSecurity]
    [DllImport(dllName)]
    public static extern bool CompressData(byte[] inputData, uint inputSize, out byte[] outputData, out uint outputSize );
    ...
    byte[] outputData;
    uint outputSize;
    bool ret = CompressData(packEntry.uncompressedData, (uint)packEntry.uncompressedData.Length, out outputData, out outputSize);

ここで、outputSize は予想どおり 60000 ですが、outputData のサイズは 1 です。バッファー C++ 側を memset すると、1 バイトしかコピーしないように見えるので、これは間違っているのでしょうか。 IntPtr + outputSize、または私がすでに持っているものを機能させるために欠けているものはありますか?

ありがとう。

4

2 に答える 2

4

2つのことがあります。

まず、P/Invoke レイヤーは C++ の参照パラメーターを処理せず、ポインターでしか機能しません。特に最後の 2 つのパラメーター (pOutputBufferおよびuOutputSize) は、正しくマーシャリングされるとは限りません。

C++ メソッド宣言を次のように変更する (またはフォームのラッパーを作成する) ことをお勧めします。

extern "C" __declspec(dllexport) bool __stdcall CompressData(  
    unsigned char* pInputData, unsigned int inSize, 
    unsigned char** pOutputBuffer, unsigned int* uOutputSize)

SAFEARRAYつまり、2 番目の問題は、P/Invoke レイヤーが、アンマネージ コードで割り当てられた "生の" 配列 (サイズを認識している COMとは対照的に) をマーシャリングする方法を認識していないという事実から生じます。 .

これは、.NET 側では、作成されたポインターをマーシャリングしてから、配列内の要素を手動でマーシャリングする必要があることを意味します (それがあなたの責任である場合は、そのように見えます)。

.NET 宣言は次のようになります。

[System.Security.SuppressUnmanagedCodeSecurity]
[DllImport(dllName)]
public static extern bool CompressData(byte[] inputData, uint inputSize, 
    ref IntPtr outputData, ref uint outputSize);

outputDataas を取得したら(これはアンマネージ メモリを指します)、次のようにクラスでメソッドIntPtrを呼び出してバイト配列に変換できます。CopyMarshal

var bytes = new byte[(int) outputSize];

// Copy.
Marshal.Copy(outputData, bytes, 0, (int) outputSize);

メモリを解放する責任がある場合は、次のようにFreeCoTaskMemメソッドを呼び出すことができます。

Marshal.FreeCoTaskMem(outputData);

もちろん、次のように、これをより適切なものにまとめることができます。

static byte[] CompressData(byte[] input, int size)
{
    // The output buffer.
    IntPtr output = IntPtr.Zero;

    // Wrap in a try/finally, to make sure unmanaged array
    // is cleaned up.
    try
    {
        // Length.
        uint length = 0;

        // Make the call.
        CompressData(input, size, ref output, ref length);

        // Allocate the bytes.
        var bytes = new byte[(int) length)];

        // Copy.
        Marshal.Copy(output, bytes, 0, bytes.Length);

        // Return the byte array.
        return bytes;
    }
    finally
    {
        // If the pointer is not zero, free.
        if (output != IntPtr.Zero) Marshal.FreeCoTaskMem(output);
    }
}
于 2012-10-29T14:48:32.267 に答える
1

pinvoke マーシャラーは、返された byte[] の大きさを推測できません。C++ のメモリへの生のポインターには、ポイント先のメモリ ブロックの検出可能なサイズがありません。これが、uOutputSize 引数を追加した理由です。クライアント プログラムには適していますが、pinvoke マーシャラーには十分ではありません。SizeParamIndexプロパティを指定して、[MarshalAs] 属性を pOutputBuffer に適用する必要があります。

配列がマーシャラーによってコピーされていることに注意してください。これはあまり望ましくありません。クライアント コードが配列を渡せるようにすることで回避できます。マーシャラーはそれを固定し、ポインターをマネージ配列に渡します。唯一の問題は、クライアント コードが配列の大きさを推測する適切な方法を持たないことです。典型的な解決策は、クライアントがそれを 2 回呼び出せるようにすることです。最初は uOutputSize = 0 で、関数は必要な配列サイズを返します。これにより、C++ 関数は次のようになります。

extern "C" __declspec(dllexport) 
int __stdcall CompressData(
     const unsigned char* pInputData, unsigned int inSize, 
     [Out]unsigned char* pOutputBuffer, unsigned int uOutputSize)
于 2012-10-29T17:14:19.767 に答える