0

我正在开发一个需要使用 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 调用会失败并出现未知的入口点异常。我包装的任何其他功能都没有发生这种情况,所以这让我有点疑惑。

4

3 回答 3

3

_GetCardStatus@28给了我一个想法。除非您在 64 位 Windows 上运行,否则您的参数数量是错误的。您的 P/Invoke forGetCardStatus将是_GetCardStatus@20,因为它有 5 个 32 位参数。您的 C 声明GetCardStatus似乎接受了cardId按值而不是按引用。由于CARDIDTYPE是 12 个字节长,这将给出参数列表 (28) 的正确长度。此外,这将解释您收到 -1073741737 (C0000057, ) 的错误代码STATUS_INVALID_PARAMETER- 因为您没有传递有效cardId-以及访问冲突 -GetCardStatus尝试写入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_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) ;

CARDIDTYPE注意三个成员的相反顺序:stdcall从左到右(即向较低地址)推送参数,我的猜测是 astruct被“推送”为一个单元。

此外,如果您稍后用 关闭打印机手柄CloseHandle,我建议将手柄CARDIDTYPE放入适当的SafeHandle,而不是裸露的IntPtr,并声明GetCardStatus接收安全手柄。

于 2009-03-29T14:22:42.357 回答
2

正如 Anton 所说,问题在于传递给函数的参数。我昨天没有注意到这一点,但是 CARDIDTYPE 结构在 GetCardID 函数中是通过指针传递的,在 GetCardStatus 函数中是通过值传递的。在我的调用中,我还通过指向 GetCardStatus 的指针传递了 CARDIDTYPE,强制 P/Invoke 框架通过指定在 Dependecy Walker 中找到的确切函数名称来定位正确的函数。

我通过将 CARDIDTYPE 定义为结构而不是类来解决这个问题,并通过引用 GetCardId 函数将其传递。此外,当传递给 GetCardStatus 函数时,CARDIDTYPE 被编组为 Struct。除了使用具有不同 pData 类型(CARD_INFO_1 和 CARD_INFO_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 Marshaller立即变量将被编组的方向。在 byte[] 的情况下,参数实际上什么也没做。它的子元素之一正在被移动。

不过,编组数组是一项棘手的工作,尤其是直接在签名与结构中使用时。使用 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 回答