我正在开发一个需要使用 P/Invoke 与本机 C API 交互的系统。现在我(又一次)偶然发现了一个我似乎无法以任何方式解决的问题。原始函数旨在根据指定使用哪种结构的参数返回两种结构。
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”时,问题就开始了。应返回的结构类型由“级别”参数定义,值为 1 应导致在“pData”中返回 CARD_INFO_1 结构。
该文档包含以下 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 包装器有问题。
我知道使用“byte[]”作为 pData 的类型可能不正确,但根据一些谷歌搜索,“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);
编辑: 我忘了提到的一件事是,如果我没有指定 EntryPoint = "_GetCardStatus@28",由于某种原因,GetCardStatus 调用会失败并出现未知的入口点异常。我包装的任何其他功能都没有发生这种情况,所以这让我有点疑惑。