我正在开发一个需要使用P / Invoke与本机C API交互的系统。现在我(又一次)偶然发现了一个我似乎无法以任何方式解决的问题。原始函数用于返回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包装器出了问题。
我知道使用“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调用将失败并出现未知的入口点异常。这已经没有发生在我已经包装的任何其他功能上,所以它让我有点疑惑。
答案 0 :(得分:3)
_GetCardStatus@28
给了我一个主意。除非您在64位Windows上运行,否则您的参数数量是错误的。 GetCardStatus
的P / Invoke为_GetCardStatus@20
,因为它有5个32位参数。您的GetCardStatus
C语句似乎接受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
从左到右推送参数(即向低地址推送),我的猜测是struct
被推送“作为一个单位。
此外,如果您稍后使用CloseHandle
关闭打印机句柄,我建议您将CARDIDTYPE
中的句柄收到相应的SafeHandle
,而不是裸IntPtr
,并声明GetCardStatus
接收安全句柄。
答案 1 :(得分:2)
正如Anton所说,问题在于传递给函数的参数。我昨天没有注意到这一点,但CARDIDTYPE结构通过GetCardID函数中的指针传递,并通过GetCardStatus函数中的值传递。在我的调用中,我也通过指向GetCardStatus的指针传递了CARDIDTYPE,强制P / Invoke框架通过指定Dependecy Walker中的确切函数名来找到正确的函数。
我通过将CARDIDTYPE定义为结构而不是类来解决这个问题,并通过引用传递给GetCardId函数。此外,CARDIDTYPE在传递给GetCardStatus函数时被封送为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);
感谢你们为解决这个问题做出的贡献: - )
答案 2 :(得分: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);
}