7

たとえば、DLL で公開されている関数のプロトタイプがあるとします。

int CALLBACK worker (char* a_inBuf, int a_InLen,
                     char** a_pOutBuf, int* a_pOutLen,
                     char** a_pErrBuf, int* a_pErrLen)

私の C# コードからその DLL 関数を呼び出すのはとてつもなく簡単だと確信していますが、このコードでは機能しません。

[DllImport("mydll.dll")]  
     public static extern int worker(
         [In, MarshalAs(UnmanagedType.LPArray)] byte[] inBuf,  
         int inputLen,  
         [Out, MarshalAs(UnmanagedType.LPArray)] byte[] outBuf,  
         out int outputLen,  
         [Out, MarshalAs(UnmanagedType.LPArray)] byte[] errBuf,  
         out int errorLen);

... 

int outputXmlLength = 0;
int errorXmlLength  = 0;

byte[]  outputXml = null;
byte[]  errorXml  = null;
worker(input, input.Length, output, out outputLength, error, out errorLength);

アンマネージ ライブラリのメモリを取得しようとすると、アクセス違反が発生します (したがって、渡されたポインタを逆参照しますoutput) 。error

*a_ppBuffer = (char*) malloc(size*sizeof(char));
  1. DLLIMPORTこの関数の C# コードにステートメント を記述するにはどうすればよいですか?

  2. 関数を実際に呼び出すにはどうすれば と にa_pOutBufアクセスa_pErrBufできnull、内部からではなくworker(つまり、実際のダブル ポインターを使用する) ことができますか?

4

1 に答える 1

8

現在の定義は機能しません。worker関数は関数内でメモリを割り当て、そのメモリに書き込みます。

P/Invoke レイヤーは、この方法で割り当てられた C スタイルの配列のマーシャリングをサポートしていません。これは、呼び出しが返されたときに配列がどれだけ大きくなるかを知る方法がないためです (たとえば、 a とは異なります) SAFEARRAY

これが、API 関数から配列へのポインターを返すことが一般的に悪い考えである理由でもあり、Windows API は、メモリ割り当てがcallerによって処理されるように記述されています。

とはいえ、の P/Invoke 宣言を次のように変更する必要がありますworker

[DllImport("mydll.dll")]  
public static extern int worker(
    [In, MarshalAs(UnmanagedType.LPArray)] byte[] inBuf,  
    int inputLen,  
    ref IntPtr outBuf, 
    ref int outputLen,  
    ref IntPtr errBuf, 
    ref int errorLen
);

これを行うことで、配列を手動でマーシャリングすることを示しています (outBufおよびerrBufパラメータが設定されます)。ポインターへの参照を渡しています (二重間接参照、それがあなたの です)。その後、境界チェックのために他のインジケーター (この場合はおよびパラメーター)char**を使用してそれらから読み取る必要があります。outputLenerrorLen

次のように、戻り時にポインターからデータをマーシャリングします。

int outputXmlLength = 0;
int errorXmlLength  = 0;

IntPtr output = IntPtr.Zero;
IntPtr error = IntPtr.Zero;

worker(
    input, 
    input.Length, 
    ref output, 
    ref outputLength, 
    ref error, 
    ref errorLength
);

// Get the strings.
string outputString = Marshal.PtrToStringAnsi(
    output, 
    outputLength
);
string errorString = Marshal.PtrToStringAnsi(
    error, 
    errorLength
);

そうは言っても、別の問題があります。メモリは関数内で割り当てられているため、メモリを解放する必要があります。を使用mallocしてメモリを割り当てているため、2 つのIntPtrインスタンスを呼び出してアンマネージ コードに戻す必要がありfreeます。

LocalAllocまたはを使用してアンマネージ コードでメモリを割り当てていた場合、クラスでそれぞれまたはメソッドCoTaskMemAllocを使用して、マネージ側のメモリを解放できます。FreeHGlobalFreeCoTaskMemMarshal

于 2012-11-19T17:33:35.687 に答える