2009-03-29 36 views
0

P/Invokeを使用してネイティブC APIとの対話が必要なシステムで作業しています。今私は(もう一度)私が決して解決できないような問題に遭遇しました。元の関数は、使用する構造体を指定するパラメータに基づいて、2種類の構造体を返すように設計されています。P/Invoke関数呼び出しの問題

次のようにCヘッダファイルは構造と機能を定義しています

#pragma pack(1) 
typedef struct { 
    DWORD JobId; 
    DWORD CardNum; 
    HANDLE hPrinter; 
} CARDIDTYPE, FAR *LPCARDIDTYPE; 
#pragma pack() 

typedef struct { 
    BOOL  bActive; 
    BOOL  bSuccess; 
} CARD_INFO_1, *PCARD_INFO_1, FAR *LPCARD_INFO_1; 

typedef struct { 
    DWORD  dwCopiesPrinted; 
    DWORD  dwRemakeAttempts; 
    SYSTEMTIME TimeCompleted; 
} CARD_INFO_2, *PCARD_INFO_2, FAR *LPCARD_INFO_2; 

BOOL ICEAPI GetCardId(HDC hdc, LPCARDIDTYPE pCardId); 
BOOL ICEAPI GetCardStatus(CARDIDTYPE CardId, DWORD level, LPBYTE pData, DWORD cbBuf, LPDWORD pcbNeeded); 

私はこのようなP /呼び出しラッパーを実装しようとしてきた:

[StructLayout(LayoutKind.Sequential, Pack=1)] 
public class CARDIDTYPE { 
    public UInt32 JobId; 
    public UInt32 CardNum; 
    public IntPtr hPrinter; 
} 

[StructLayout(LayoutKind.Sequential)] 
public class CARD_INFO_1 { 
    public bool bActive; 
    public bool bSuccess; 
} 

[StructLayout(LayoutKind.Sequential)] 
public class CARD_INFO_2 { 
    public UInt32 dwCopiesPrinted; 
    public UInt32 dwRemakeAttempts; 
    public Win32Util.SYSTEMTIME TimeCompleted; 
} 
[DllImport("ICE_API.DLL", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi, SetLastError = true)] 
public static extern bool GetCardId(HandleRef hDC, [Out]CARDIDTYPE pCardId); 

[DllImport("ICE_API.DLL", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi, SetLastError = true)] 
public static extern bool GetCardStatus(CARDIDTYPE CardId, UInt32 level, [Out] byte[] pData, UInt32 cbBuf, out UInt32 pcbNeeded); 

「GetCardId」は動作しているように電話良い。私はそれを呼び出した後、CARDIDTYPEインスタンスにもっともらしいデータを取得します。しかし、私が "GetCardStatus"を呼び出すと、問題が始まります。返される構造体の型は "level"パラメータによって定義され、値1は "pData"に返されるCARD_INFO_1構造体になります。

CARD_INFO_1 ci1; 
DWORD cbNeeded; 
ci1.bActive = TRUE; 
if (GetCardStatus(*lpCardID, 1, (LPBYTE)&ci1, sizeof(ci1), &cbNeeded)) { /* success */ } 

マイ同等のC#実装は次のようである:私はこのC#コードを実行すると

uint needed; 
byte[] byteArray = new byte[Marshal.SizeOf(typeof(CARD_INFO_1))]; 
if (GetCardStatus(cardId, 1, byteArray, (uint)byteArray.Length, out needed)) { /* success */ } 

は、方法が偽とMarshal.GetLastWin32Error返す(

ドキュメントは、次のCの例が含まれてい)返す-1073741737(これは私には分かりません)。この呼び出しが失敗する理由はなく、間違いなくこのエラーコードはありません。だから私はP/Invokeラッパーに何か間違っていると思う。

pDataのタイプとして「byte []」を使用するのはおそらく正しいとは限りませんが、グーグルによれば「LPBYTE」は「[Out] byte []」に変換されます。これを行う正しい方法は、pDataをIntPtrとして使用し、Marshal.PtrToStructure(...)を使用して構造体を作成することです。私はこれを試しましたが、結果は同じです。ここでは、このシナリオのためのコードは次のとおりです。

[DllImport(@"ICE_API.DLL", CharSet = CharSet.Auto, EntryPoint = "[email protected]", CallingConvention = CallingConvention.Winapi, SetLastError = true)] 
public static extern bool GetCardStatus(CARDIDTYPE CardId, UInt32 level, IntPtr pData, UInt32 cbBuf, out UInt32 pcbNeeded); 

uint needed; 
int memSize = Marshal.SizeOf(typeof(CARD_INFO_1)); 
IntPtr memPtr = Marshal.AllocHGlobal(memSize); 
if (!GetCardStatus(cardId, 1, memPtr, (uint)memSize, out needed)) { 
    int lastError = Marshal.GetLastWin32Error(); 
    // error code is -1073741737 
} 
CARD_INFO_1 info = (CARD_INFO_1)Marshal.PtrToStructure(memPtr, typeof(CARD_INFO_1)); 
Marshal.FreeHGlobal(memPtr); 

編集:私は言及を忘れてしまった ことの一つは、私がエントリーポイント=「_GetCardStatusを指定しない場合は、何らかの理由でGetCardStatusコールが不明なエントリポイント例外で失敗するということです@ 28 "。これは、私がラップした他の機能には起こっていないので、ちょっと疑問に思いました。

答えて

3

[email protected]私に考えを与えました。 64ビットWindowsで実行している場合を除き、引数の数が間違っています。 GetCardStatusのP/Invokeは、5つの32ビット引数を持つため、[email protected]になります。GetCardStatusのC宣言は、参照ではなく値でcardIdを受け入れているようです。 CARDIDTYPEは12バイトの長さなので、引数リストの正しい長さが得られます(28)。さらに、有効なcardId-をアクセス違反 - GetCardStatuspcbNeededに書き込もうとしているため、エラーコード-1073741737(C0000057、STATUS_INVALID_PARAMETER)を受け取ったことが説明されます。マーシャラーそれを押したことさえしていない!

エルゴ:3人のCARDIDTYPEメンバーの

[DllImport("ICE_API.DLL", CharSet = CharSet.Auto, 
    CallingConvention = CallingConvention.Winapi, SetLastError = true)] 
public static extern bool GetCardStatus (
    IntPtr hPrinter, UInt32 cardNum, UInt32 jobId, UInt32 level, 
    [In, Out] CARD_INFO_1 data, UInt32 cbBuf, out UInt32 pcbNeeded) ; 
[DllImport("ICE_API.DLL", CharSet = CharSet.Auto, 
    CallingConvention = CallingConvention.Winapi, SetLastError = true)] 
public static extern bool GetCardStatus (
    IntPtr hPrinter, UInt32 cardNum, UInt32 jobId, UInt32 level, 
    [In, Out] CARD_INFO_2 data, UInt32 cbBuf, out UInt32 pcbNeeded) ; 

ノート逆の順序は:stdcallは左から右に(すなわち下位アドレスに向かって)パラメータをプッシュして、私の推測ではstructとして「プッシュ」されていることですユニット。後でCloseHandleとプリンタハンドルを閉じた場合

また、私がいない裸IntPtrに、適切なSafeHandleCARDIDTYPEにハンドルを受け、安全なハンドルを受信するGetCardStatusを宣言することをお勧めしたいです。

+0

これは実際に私が最初にしたことでした。ここで説明するように署名を使用すると、例外が発生します。 System.AccessViolationException:保護されたメモリの読み取りまたは書き込みを試みました。これはしばしば、他のメモリが壊れていることを示します。 –

+0

私はあなたの質問をより慎重に読んで、私の答えを編集しました。 –

+0

ご意見ありがとうございます。あなたの答えは結局問題を解決するように私を導いた。私は新しい答えで完全な解決策を投稿します: –

1

問題は、使用しているある[出力]属性は即時変数が整列化される方向にCLRマーシャラーを教えてあなたは何

[DllImport("ICE_API.DLL", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi, SetLastError = true)] 
public static extern bool GetCardStatus(CARDIDTYPE CardId, UInt32 level, byte[] pData, UInt32 cbBuf, out UInt32 pcbNeeded); 

アウトを使用すべきではない場所を/で。 byte []の場合、パラメータは実際に何もしません。そのうちの1つが移動されています。

マーシャルアレイは、特にシグネチャと構造の両方に直接使用すると、扱いにくいビジネスです。 IntPtrを使用し、そこにメモリを割り当て、手動で配列をIntPtrからマーシャリングする方がよい場合があります。

[DllImport("ICE_API.DLL", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi, SetLastError = true)] 
public static extern bool GetCardStatus(CARDIDTYPE CardId, UInt32 level, IntPtr pData, UInt32 cbBuf, out UInt32 pcbNeeded); 

public void Example(uint size) { 
    // Get other params 
    var ptr = Marshal.AllocHGlobal(size); 
    GetCardStatus(cardId, level, ptr, size, out needed); 
    // Marshal back the byte array here 
    Marshal.FreeHGlobal(ptr); 
} 
+0

:-)この問題を解決するためのあなたの貢献のためにあなたの両方をありがとうございます。ファンクションはfalseを返し、エラーコードは同じです。 –

+0

@Johnny、最初または2番目のソリューションですか? – JaredPar

+0

申し訳ありません... 2番目の解決策です。私はこのコードを含めるために投稿を編集します。 –

2

Antonは、問題が関数に渡されるパラメータにあると示唆しています。私はこれを昨日気付かなかったが、CARDIDTYPE構造体はGetCardID関数のポインタとGetCardStatus関数の値によって渡されます。私の呼び出しでは、GetCardStatusへのポインタによってCARDIDTYPEを渡し、Dependecy Walkerにある正確な関数名を指定してP/Invokeフレームワークが正しい関数を見つけるようにしました。

私は、CARDIDTYPEをクラスではなく構造体として定義し、GetCardId関数を参照して渡すことでこれを解決しました。さらに、CARDIDTYPEはGetCardStatus関数に渡されるときにStructとしてマーシャリングされます。これは、異なるpDataタイプ(CARD_INFO_1とCARD_INFO_2)を持つ2つの関数定義を使用するAntons技術に加えて、正しく動作するようになりました。ここでは、最終的な定義がされています

[StructLayout(LayoutKind.Sequential, Pack = 1)] 
public struct CARDIDTYPE { 
    public UInt32 JobId; 
    public UInt32 CardNum; 
    public IntPtr hPrinter; 
} 

[StructLayout(LayoutKind.Sequential)] 
public class CARD_INFO_1 { 
    public bool bActive; 
    public bool bSuccess; 
} 

[StructLayout(LayoutKind.Sequential)] 
public class CARD_INFO_2 { 
    public UInt32 dwCopiesPrinted; 
    public UInt32 dwRemakeAttempts; 
    public Win32Util.SYSTEMTIME TimeCompleted; 
} 

[DllImport("ICE_API.DLL", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi, SetLastError = true)] 
public static extern bool GetCardId(HandleRef hDC, ref CARDIDTYPE pCardId); 

[DllImport(@"ICE_API.DLL", EntryPoint = "GetCardStatus", CallingConvention = CallingConvention.Winapi, SetLastError = true)] 
public static extern bool GetCardStatus([MarshalAs(UnmanagedType.Struct)]CARDIDTYPE CardId, UInt32 level, 
    [In, Out] CARD_INFO_1 pData, UInt32 cbBuf, out UInt32 pcbNeeded); 

[DllImport(@"ICE_API.DLL", EntryPoint = "GetCardStatus", CallingConvention = CallingConvention.Winapi, SetLastError = true)] 
public static extern bool GetCardStatus([MarshalAs(UnmanagedType.Struct)]CARDIDTYPE CardId, UInt32 level, 
    [In, Out] CARD_INFO_2 pData, UInt32 cbBuf, out UInt32 pcbNeeded); 

は、私がしようとこれを持っていますが、結果は同じである

+0

Heh、UnmanagedTypeについて知りませんでした。ヒントをありがとう! :) –