0

P/Invokeを使用してネイティブCAPIとの対話を必要とするシステムに取り組んでいます。今、私は(まだ)私が決して解決することができないように思われる問題に出くわしました。元の関数は、使用する構造を指定するパラメーターに基づいて、2種類の構造を返すように設計されています。

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/Invokeラッパーを実装しようとしました。

[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の場合、CARD_INFO_1構造が「pData」で返されます。

ドキュメントには、次のCの例が含まれています。

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

私の同等のC#実装は次のようになります:

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

このC#コードを実行すると、メソッドはfalseを返し、Marshal.GetLastWin32Error()は-1073741737を返します(これは私にはあまり意味がありません)。この呼び出しが失敗する理由はわかりません。また、このエラーコードでは間違いありません。そのため、P/Invokeラッパーに問題があると思われます。

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

[DllImport(@"ICE_API.DLL", CharSet = CharSet.Auto, EntryPoint = "_GetCardStatus@28", 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);

編集: 私が言及するのを忘れた1つのことは、EntryPoint = "_GetCardStatus @ 28"を指定しない場合、何らかの理由でGetCardStatus呼び出しが不明なエントリポイント例外で失敗することです。これは私がラップした他の関数には起こらなかったので、少し疑問に思いました。

4

3 に答える 3

3

_GetCardStatus@28私にアイデアをくれました。64ビットWindowsで実行していない限り、引数の数が間違っています。P / Invoke forGetCardStatus_GetCardStatus@20、32ビットの引数が5つあるため、になります。のC宣言は、参照ではなく値でGetCardStatus受け入れるようです。cardIdは12バイトの長さなのでCARDIDTYPE、これにより引数リストの正しい長さが得られます(28)。さらに、これは、有効なSTATUS_INVALID_PARAMETERを渡していないために-1073741737(C0000057、)のエラーコードを受信すること、アクセス違反が書き込みを試みることの両方を説明します。これは、マーシャラーがプッシュしていないため、ゴミです。 !!cardIdGetCardStatuspcbNeeded

エルゴ:

[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) ;

CARDIDTYPE3つのメンバーの逆の順序に注意してください。stdcallパラメーターを左から右に(つまり、下位アドレスに向かって)プッシュします。私の推測では、astructは1つの単位として「プッシュ」されます。

また、後ででプリンタハンドルを閉じる場合は、ハンドルを裸ではなく適切な場所に受け取り、安全なハンドルを受け取ることを宣言することをおCloseHandle勧めします。CARDIDTYPESafeHandleIntPtrGetCardStatus

于 2009-03-29T14:22:42.357 に答える
2

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

これを解決するには、CARDIDTYPE をクラスではなく構造体として定義し、参照によって GetCardId 関数に渡します。さらに、GetCardStatus 関数に渡されると、CARDIDTYPE は 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);

この問題を解決するために貢献してくれてありがとう:-)

于 2009-03-30T10:26:45.270 に答える
1

問題は、何も使用しないはずの[Out]を使用していることです。

[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);

Out / In属性は、CLRマーシャラーにイミディエート変数がマーシャリングされる方向を通知します。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);
}
于 2009-03-29T14:16:06.337 に答える