I am working on a system that requires interaction with a native C API using P/Invoke. Now I've (yet again) stumbled upon a problem which I cannot seem to solve in any way. The original function is designed to return 2 kinds of structures, based on a parameter that specifies which structure to use.
The C header file defines the structures and function as follows:
#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 );
I have attempted to implement P/Invoke wrappers like this:
[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);
Calling the "GetCardId" seems to work fine. I get plausible data in CARDIDTYPE instance after calling it. However when I call "GetCardStatus" the problems start. The type of structure that should be returned is defined by the "level" param, and a value of 1 should result in a CARD_INFO_1 structure to be returnes in "pData".
The documentation contains the following C example:
CARD_INFO_1 ci1;
DWORD cbNeeded;
ci1.bActive = TRUE;
if (GetCardStatus(*lpCardID, 1, (LPBYTE)&ci1, sizeof(ci1), &cbNeeded )) { /* success */ }
My equivalent C# implementation is like this:
uint needed;
byte[] byteArray = new byte[Marshal.SizeOf(typeof(CARD_INFO_1))];
if (GetCardStatus(cardId, 1, byteArray, (uint)byteArray.Length, out needed)) { /* success */ }
When I execute this C# code, the method returns false and Marshal.GetLastWin32Error() return -1073741737 (which does not make much sense to me). I see no reason why this call should fail, and definitely not with this error code. So I suspect I have got something wrong in my P/Invoke wrapper.
I know that using "byte[]" as the type of pData is probably not correct, but according to some googling a "LPBYTE" translates to "[Out] byte[]". I guess the correct way to do this is to have pData as an IntPtr, and create the structure using Marshal.PtrToStructure(...). I have tried this, but the result is the same. Here is the code for this scenario:
[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);
Edit: One thing I forgot to mention is that for some reason the GetCardStatus call fails with an unknown entry point exception if I do not specify EntryPoint = "_GetCardStatus@28". This has not happened to any other function I have wrapped, so it got me wondering a bit.