C# P/Invoke函数调用问题

C# P/Invoke函数调用问题,c#,function,struct,pinvoke,C#,Function,Struct,Pinvoke,我正在处理一个需要使用p/Invoke与本机C API交互的系统。现在我(又一次)偶然发现了一个我似乎无法以任何方式解决的问题。原始函数设计为基于指定要使用的结构的参数返回两种结构 C头文件定义了如下结构和功能: #pragma pack(1) typedef struct { DWORD JobId; DWORD CardNum; HANDLE hPrinter; } CARDIDTYPE, FAR *LPCARDIDTYPE; #pragma pack() typede

我正在处理一个需要使用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);
uint needed;
byte[] byteArray = new byte[Marshal.SizeOf(typeof(CARD_INFO_1))];
if (GetCardStatus(cardId, 1, byteArray, (uint)byteArray.Length, out needed)) { /* success */ }
打电话给“GetCardd”似乎效果不错。调用CardType实例后,我在该实例中获得了合理的数据。然而,当我调用“GetCardStatus”时,问题就开始了。应返回的结构类型由“level”参数定义,值为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#实现如下所示:

[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);
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调用将失败,并出现未知的入口点异常_GetCardStatus@28". 我包装的任何其他函数都没有出现这种情况,因此我有点疑惑。

问题是您正在使用[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封送器立即变量将被封送到哪个方向。在字节[]的情况下,参数实际上什么也不做。它的一个子元素正在移动

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

问题是你在使用[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封送器立即变量将被封送到哪个方向。在字节[]的情况下,参数实际上什么也不做。它的一个子元素正在移动

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

_GetCardStatus@28
给了我一个想法。除非您在64位Windows上运行,否则您的参数数量是错误的。您对
GetCardStatus
的P/Invoke将是
_GetCardStatus@20
,因为它有5个32位参数。您的
GetCardStatus
的C声明似乎通过值而不是引用来接受
carid
。由于
carddType
的长度为12字节,因此这将给出参数列表的正确长度(28)。此外,这将解释您收到的错误代码为-1073741737(C0000057,
STATUS\u INVALID\u PARAMETER
)-因为您没有传递有效的
cardId
-以及访问冲突-
GetCardStatus
试图写入
pcbNeeded
,这是垃圾,因为元帅还没推它

因此:

请注意,三个
carddType
成员的顺序相反:
stdcall
将参数从左向右推送(即向较低的地址推送),我猜
struct
是作为一个单元“推送”的


另外,如果您稍后使用
CloseHandle
关闭打印机手柄,我建议将
carddType
中的手柄接收到适当的
SafeHandle
,而不是裸
IntPtr
,并声明
GetCardStatus
以接收安全手柄。

_GetCardStatus@28
给了我一个想法。除非您在64位Windows上运行,否则您的参数数量是错误的。您对
GetCardStatus
的P/Invoke将是
_GetCardStatus@20
,因为它有5个32位参数。您的
GetCardStatus
的C声明似乎通过值而不是引用来接受
carid
。由于
carddType
的长度为12字节,因此这将给出参数列表的正确长度(28)。此外,这将解释您收到的错误代码为-1073741737(C0000057,
STATUS\u INVALID\u PARAMETER
)-因为您没有传递有效的
cardId
-以及访问冲突-
GetCardStatus
试图写入
pcbNeeded
,这是垃圾,因为元帅还没推它

因此:

请注意,三个
carddType
成员的顺序相反:
stdcall
将参数从左向右推送(即向较低的地址推送),我猜
struct
是作为一个单元“推送”的


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

正如Anton所说,问题在于传递给函数的参数。我昨天没有注意到这一点,但是CardType结构在GetCardID函数中通过指针传递,在GetCardStatus函数中通过值传递。在我的通话中,我传递了CarddType