I am trying to write a C# P/Invoke wrapper for a C API (a native Win dll), and generally this is working fine. The only exception is a specific method which takes a struct as a parameter in the C code. The function is invoked without any exceptions, but it returns false indicating that something failed in the execution.
In the API header file the involved method and structs are defined as follows:
#define MAX_ICE_MS_TRACK_LENGTH 256
typedef struct tagTRACKDATA
{
UINT nLength;
BYTE TrackData[MAX_ICE_MS_TRACK_LENGTH];
} TRACKDATA, FAR* LPTRACKDATA;
typedef const LPTRACKDATA LPCTRACKDATA;
BOOL ICEAPI EncodeMagstripe(HDC /*hDC*/,
LPCTRACKDATA /*pTrack1*/,
LPCTRACKDATA /*pTrack2*/,
LPCTRACKDATA /*pTrack3*/,
LPCTRACKDATA /*reserved*/);
I have made an attempt to create a C# P/Invoke wrapper using the following code:
public const int MAX_ICE_MS_TRACK_LENGTH = 256;
[StructLayout(LayoutKind.Sequential)]
public class MSTrackData {
public UInt32 nLength;
public readonly Byte[] TrackData = new byte[MAX_ICE_MS_TRACK_LENGTH];
}
[DllImport("ICE_API.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool EncodeMagstripe(IntPtr hDC,
[In]ref MSTrackData pTrack1,
[In]ref MSTrackData pTrack2,
[In]ref MSTrackData pTrack3,
[In]ref MSTrackData reserved);
Then I try to invoke the EncodeMagstripe method using the following C# code:
CardApi.MSTrackData trackNull = null;
CardApi.MSTrackData track2 = new CardApi.TrackData();
byte[] trackBytes = Encoding.ASCII.GetBytes(";0123456789?");
track2.nLength = (uint)trackBytes.Length;
Buffer.BlockCopy(trackBytes, 0, track2.TrackData, 0, trackBytes.Length);
if (!CardApi.EncodeMagstripe(hDC, ref trackNull, ref track2, ref trackNull, ref trackNull)) {
throw new ApplicationException("EncodeMagstripe failed", Marshal.GetLastWin32Error());
}
This causes an ApplicationException to be thrown, and the error code is 801 which according to the documentation means "Data includes too many characters for the selected Track 2 format.". However the selected track format should allow up to 39 characters (I have also tried shorter strings).
I suspect the problem occurrs due to something I did wrong in the MSTrackData definition, but I cannot see what this may be. Does anyone have any suggestions?
All the answers given so far have a bit of the answer but are incomplete. You need the MarshalAs - ByValArray as well as the new, your MSTrackDatas are already references so you do not need to pass them by ref and you must check what calling convention ICEAPI represents, if it is StdCall you don't need to change anything but if it is cdecl you will need to add the CallingConvention to your DllImport attribute. Also, you may need to add a MarshalAs attribute to your bool return value to make sure it is marshaled as 4 byte WinApi style bool. Here are the declares you'll (probably) need:
public const int MAX_ICE_MS_TRACK_LENGTH = 256;
[StructLayout(LayoutKind.Sequential)]
public class MSTrackData {
public UInt32 nLength;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
public Byte[] TrackData = new byte[MAX_ICE_MS_TRACK_LENGTH];
}
[DllImport("ICE_API.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EncodeMagstripe(IntPtr hDC,
[In] MSTrackData pTrack1,
[In] MSTrackData pTrack2,
[In] MSTrackData pTrack3,
[In] MSTrackData reserved);
I would define the BYTE array not with new, but use the following code instead to initialise the right size:
[MarshalAs(UnmanagedType.byValTSt, SizeConst =256)]
public readonly Byte[] TrackData;
I have used this successfully on char arrays in the past.
Looks to me like the problem is that you're passing a reference by reference. Since MSTrackData is a class (i.e. reference type), passing it by reference is like passing a pointer-to-pointer.
Change your managed prototype to:
public static extern bool EncodeMagstripe(IntPtr hDC,
MSTrackData pTrack1,
MSTrackData pTrack2,
MSTrackData pTrack3,
MSTrackData reserved);
See the MSDN article about passing structures.
I had almost exactly the same problem - but with ReadMagstripe. And the solution provided here for EncodeMagstripe did not work for ReadMagstripe! I think the reason it did not work was that ReadMagstripe has to return data into the TRACKDATA structure/class, while EncodeMagstripe only passes data to the dll and data in TRACKDATA does not need to be changed. Here is the implementation that eventually worked for me - both with EncodeMagstripe and ReadMagstripe:
public const int MAX_ICE_MS_TRACK_LENGTH = 256;
[StructLayout(LayoutKind.Sequential)]
public struct TRACKDATA
{
public UInt32 nLength;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string szTrackData;
}
[DllImport("ICE_API.dll", EntryPoint="_ReadMagstripe#20", CharSet=CharSet.Auto,
CallingConvention=CallingConvention.Winapi, SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ReadMagstripe(int hdc, ref TRACKDATA ptrack1, ref TRACKDATA ptrack2,
ref TRACKDATA ptrack3, ref TRACKDATA reserved);
[DllImport("ICE_API.dll", EntryPoint="_EncodeMagstripe#20", CharSet=CharSet.Auto,
CallingConvention = CallingConvention.Winapi, SetLastError=true)]
public static extern bool EncodeMagstripe(int hdc, [In] ref TRACKDATA ptrack1, [In] ref TRACKDATA ptrack2,
[In] ref TRACKDATA ptrack3, [In] ref TRACKDATA reserved);
/*
....
*/
private void EncodeMagstripe()
{
ICE_API.TRACKDATA track1Data = new ICE_API.TRACKDATA();
ICE_API.TRACKDATA track2Data = new ICE_API.TRACKDATA();
ICE_API.TRACKDATA track3Data = new ICE_API.TRACKDATA();
ICE_API.TRACKDATA reserved = new ICE_API.TRACKDATA();
//if read magstripe
bool bRes = ICE_API.ReadMagstripe(printer.Hdc, ref track1Data, ref track2Data,
ref track3Data, ref reserved);
//encode magstripe
if (bRes)
{
track2Data.szTrackData = "1234567890";
track2Data.nLength = 10;
bRes = ICE_API.EncodeMagstripe(printer.Hdc, ref track1Data, ref track2Data, ref track3Data, ref reserved);
}
}
Related
Used InternetOpen with a set proxy server (127.0.0.1:5401) and bypass (<-loopback>) but, when doing InternetQueryOption with the option set to INTERNET_OPTION_PROXY, the returned strings, in the INTERNET_PROXY_INFO struct, are empty, but the access type is correct, INTERNET_OPEN_TYPE_PROXY.
here is the sig for InternetQueryOption and the INTERNET_PROXY_INFO struct, maybe I am doing something wrong:
[DllImport("wininet.dll", SetLastError = true)]
public static extern bool InternetQueryOption([In] IntPtr hInternet, [In] INTERNET_OPTION option, [In, Out] IntPtr buffer, [In, Out] ref int bufferSize);
public enum INTERNET_OPTION : uint
{
PROXY = 38
}
public enum INTERNET_OPEN_TYPE : uint
{
PRECONFIG,
DIRECT,
PROXY = 3,
PRECONFIG_WITH_NO_AUTOPROXY
}
[StructLayout(LayoutKind.Sequential)]
public struct INTERNET_PROXY_INFO
{
public INTERNET_OPEN_TYPE accessType;
private fixed char proxy[500];
private fixed char proxyBypass[500];
}
Note that I have tried every possible variant of the struct:
1) proxy and proxyBypass being string with MarshalAs(ByValTStr, 500);
2) proxy and proxyBypass being string and assigning to them a string which is 500 of the 0 characters;
3) The above 2 variants but with the struct being a class.
All 3 return the correct accessType of PROXY but the other 2 fields are empty strings.
INTERNET_PROXY_INFO needs to be a big blob of memory, WinINet will set the string pointers for you.
public class WinApi
{
public enum INTERNET_OPTION : uint { PROXY = 38 }
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct INTERNET_PROXY_INFOA
{
public IntPtr accessType;
public string proxy;
public string proxyBypass;
}
[DllImport("wininet.dll", SetLastError = true)]
public static extern int InternetQueryOptionA([In] IntPtr hInternet, [In] INTERNET_OPTION option, [In, Out] IntPtr buffer, [In, Out] ref int bufferSize);
}
You could allocate a blob of memory and hope it is large enough or ask the function:
int cb = 0;
WinApi.InternetQueryOptionA(IntPtr.Zero, WinApi.INTERNET_OPTION.PROXY, IntPtr.Zero, ref cb);
IntPtr data = Marshal.AllocCoTaskMem(cb);
int succ = WinApi.InternetQueryOptionA(IntPtr.Zero, WinApi.INTERNET_OPTION.PROXY, data, ref cb);
if (succ != 0)
{
WinApi.INTERNET_PROXY_INFOA info = (WinApi.INTERNET_PROXY_INFOA) Marshal.PtrToStructure(data, typeof(WinApi.INTERNET_PROXY_INFOA));
Console.WriteLine(string.Format("{2}: {0}|{1}", info.proxy, info.proxyBypass, info.accessType));
}
Marshal.FreeCoTaskMem(data);
I am new to this concept of calling C++ methods from C#.
Assuming that I want to call a C++ function GetThreadWaitChain from C#:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms679364(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/ms681623(v=vs.85).aspx
And I've built a model of other types on which the call depends:
[DllImport("Advapi32.dll")]
public static extern void CloseThreadWaitChainSession(IntPtr WctHandle);
[DllImport("Advapi32.dll")]
public static extern HANDLE OpenThreadWaitChainSession(UInt32 Flags, UInt32 callback);
[DllImport("Advapi32.dll")]
public static extern BOOL GetThreadWaitChain(
IntPtr WctHandle,
UInt32 Context,
UInt32 flags,
UInt32 ThreadId,
WAITCHAIN_NODE_INFO NodeInfoArray,
UInt32 IsCycle
);
[StructLayout(LayoutKind.Sequential)]
public struct WAITCHAIN_NODE_INFO
{
public UInt32 ObjectType;
public UInt32 ObjectStatus;
public struct LockObject
{
string ObjectName;
UInt64 Timeout;
UInt32 Alertable;
}
public struct ThreadObject
{
UInt32 ProcessId;
UInt32 ThreadId;
UInt32 WaitTime;
UInt32 ContextSwitches;
}
}
How can I call GetThreadWaitChain function? It accepts a pointer to WAITCHAIN_NODE_INFO struct...
Currently, that's how I figured out to call the function (obviously it doesn't works) :
void CollectWaitInformation(int threadId)
{
var wctHandle = OpenThreadWaitChainSession(0, 0);
WAITCHAIN_NODE_INFO info = new WAITCHAIN_NODE_INFO();
var result = GetThreadWaitChain(wctHandle, 0,
GetThreadWaitChainFlags.WCT_OUT_OF_PROC_COM_FLAG, threadID, info , 0);
}
Are my C++ types mapping to C# types right?
The GetThreadWaitChain function prototype is incorrect.
It should be:
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool GetThreadWaitChain(
IntPtr WctHandle,
IntPtr Context,
UInt32 Flags,
int ThreadId,
ref int NodeCount,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4)]
[In, Out]
WAITCHAIN_NODE_INFO[] NodeInfoArray,
out int IsCycle
);
When calling first allocate the WAITCHAIN_NODE_INFO array.
WAITCHAIN_NODE_INFO[] data = new WAITCHAIN_NODE_INFO[16];
After running my silverlight 5.0 application with several PInvokes in it for 5 minutes or so, I get the following error:
Attempted to read or write protected memory
Likely, I'm getting a memory leak somewhere.
The problem is how do I debug this? I don't get a stacktrace at all so I can't pinpoint the offending code exactly.
By commenting out code and rerunning application several times, I think I managed to track down the problem but I can't figure out the problem.
Potentially Offending Code:
private void InitUSBEvents()
{
const string clsName = "SLUsbClass";
const string wndName = "SLUsbWindow";
Win32.WNDCLASSEX wndClassEx = new Win32.WNDCLASSEX();
wndClassEx.cbSize = Marshal.SizeOf(wndClassEx);
wndClassEx.lpszClassName = clsName;
wndClassEx.lpfnWndProc = WndProc;
rClassAtomValue = Win32.RegisterClassEx2(ref wndClassEx);
windowHandle = Win32.CreateWindowEx2(0, rClassAtomValue, wndName, 0, 0, 0, 0, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
Win32Usb.RegisterKeyboardUsbEvents(windowHandle);
}
[AllowReversePInvokeCalls]
private IntPtr WndProc(IntPtr hWnd, WM msg, IntPtr wParam, IntPtr lParam)
{
switch (msg)
{
case WM.INPUT:
//Console.WriteLine("Key Event");
break;
default:
return Win32.DefWindowProc(hWnd, msg, wParam, lParam);
}
return IntPtr.Zero;
}
PInvoke Related Declarations:
public class Win32
{
[DllImport("user32.dll", SetLastError = true, EntryPoint = "CreateWindowEx")]
public static extern IntPtr CreateWindowEx(
WindowStylesEx dwExStyle,
string lpClassName,
string lpWindowName,
WindowStyles dwStyle,
int x,
int y,
int nWidth,
int nHeight,
IntPtr hWndParent,
IntPtr hMenu,
IntPtr hInstance,
IntPtr lpParam);
// Create a window, but accept a atom value.
[DllImport("user32.dll", SetLastError = true, EntryPoint = "CreateWindowEx")]
public static extern IntPtr CreateWindowEx2(
WindowStylesEx dwExStyle,
UInt16 lpClassName,
string lpWindowName,
WindowStyles dwStyle,
int x,
int y,
int nWidth,
int nHeight,
IntPtr hWndParent,
IntPtr hMenu,
IntPtr hInstance,
IntPtr lpParam);
[DllImport("kernel32.dll", EntryPoint = "LocalAlloc")]
internal static extern IntPtr LocalAlloc_NoSafeHandle(
LocalMemoryFlags uFlags, IntPtr sizetdwBytes);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern IntPtr LocalFree(IntPtr hMem);
[DllImport("user32.dll", SetLastError = true, EntryPoint = "RegisterClassEx")]
public static extern UInt16 RegisterClassEx2([In] ref WNDCLASSEX lpwcx);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.U2)]
public static extern short RegisterClassEx([In] ref WNDCLASSEX lpwcx);
[DllImport("user32.dll")]
public static extern IntPtr DefWindowProc(IntPtr hWnd, WM uMsg, IntPtr wParam, IntPtr lParam);
}
public class Win32Usb
{
public static bool RegisterKeyboardUsbEvents(IntPtr hWnd)
{
//Create an array of all the raw input devices we want to
//listen to. In this case, only keyboard devices.
//RIDEV_INPUTSINK determines that the window will continue
//to receive messages even when it doesn't have the focus.
RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[1];
rid[0].usUsagePage = 0x01;
rid[0].usUsage = 0x06;
rid[0].dwFlags = RIDEV_INPUTSINK;
rid[0].hwndTarget = hWnd;
return RegisterRawInputDevices(rid, (uint)rid.Length, (uint)Marshal.SizeOf(rid[0]));
}
[StructLayout(LayoutKind.Explicit)]
internal class RAWINPUT
{
[FieldOffset(0)]
public RAWINPUTHEADER header;
[FieldOffset(16)]
public RAWMOUSE mouse;
[FieldOffset(16)]
public RAWKEYBOARD keyboard;
[FieldOffset(16)]
public RAWHID hid;
}
[StructLayout(LayoutKind.Sequential)]
internal class RAWINPUTDEVICELIST
{
public IntPtr hDevice;
[MarshalAs(UnmanagedType.U4)]
public int dwType;
}
[StructLayout(LayoutKind.Sequential)]
internal struct RAWINPUTDEVICE
{
[MarshalAs(UnmanagedType.U2)]
public ushort usUsagePage;
[MarshalAs(UnmanagedType.U2)]
public ushort usUsage;
[MarshalAs(UnmanagedType.U4)]
public int dwFlags;
public IntPtr hwndTarget;
}
}
I can't really find anything wrong with the PInvoke declarations either. What are some good strategies to debug PInvoke related errors on Silverlight? Or is there a way to force the stacktrace to output? (I've tried turning on all exceptions to be thrown in Debug->Exception)
The WndProc delegate that you are passing in the wndClassEx struct is being garbage collected. When you pass a delegate to unmanaged code that delegate will only be kept alive for the time of the call to the unmanaged function. Once the call ends there is no longer reference to it in your Silverlight application so the garbage collection considers it dead and will collect it.
This example of creating a window class in Silverlight caches the WndProc delegate in a static method so that it does not get garbage collected.
Also see this (older, but still valid) MSDN article on P/Invoke.
#shf301 is correct that you need to take steps to stop your delegate from being collected. I won't attempt to repeat his point here.
However, on top of that issue, your structs are incorrectly defined. They will work under 32 bit, but not under 64 bit. The rule of thumb with FieldOffset is that you only ever use it with an offset of 0. This rule is what allows you to write code that works on both 32 and 64 bit. In your code you used an offset of 16 which is fine for 32 bit, but not 64 bit. Define your structures following this pattern:
[StructLayout(LayoutKind.Explicit)]
struct RAWINPUTUNION
{
[FieldOffset(0)]
public RAWMOUSE mouse;
[FieldOffset(0)]
public RAWKEYBOARD keyboard;
[FieldOffset(0)]
public RAWHID hid;
}
[StructLayout(LayoutKind.Sequential)]
struct RAWINPUT
{
public RAWINPUTHEADER header;
public RAWINPUTUNION data;
}
I am trying to PInvoke CreateDesktop in a way that passes the flag to inherit the desktop by child processes. The declaration is as follows:
[DllImport("user32", EntryPoint = "CreateDesktopW", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr CreateDesktop(string lpszDesktop, IntPtr lpszDevice, IntPtr pDevmode, int dwFlags,
int dwDesiredAccess, [MarshalAs(UnmanagedType.LPStruct)] SECURITY_ATTRIBUTES lpsa);
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
And I use it as follows:
Win32.SECURITY_ATTRIBUTES sa = new Win32.SECURITY_ATTRIBUTES();
sa.nLength = Marshal.SizeOf(sa);
sa.bInheritHandle = 1;
testDesktopHandle = Win32.CreateDesktop(name, IntPtr.Zero, IntPtr.Zero, 0, Win32.GENERIC_ALL, sa);
And it unfortunately doesn't work, I get the following error:
System.Runtime.InteropServices.MarshalDirectiveException: Cannot marshal 'parameter #6': Invalid managed/unmanaged type combination (this value type must be paired with Struct).
Any ideas what I am doing wrong?
Try changing parameter #6 to
static extern IntPtr CreateDesktop(..., [In] ref SECURITY_ATTRIBUTES lpsa);
(This compiles and doesn't throw an exception at runtime, but I've tested it only with bogus arguments.)
Compare with C++ declaration of CreateDesktop:
HDESK WINAPI CreateDesktop(..., __in_opt LPSECURITY_ATTRIBUTES lpsa);
↑ ↑ ↑
[In] ref SECURITY_ATTRIBUTES lpsa
LP stands for "long pointer", i.e. LPSECURITY_ATTRIBUTES is a pointer to a SECURITY_ATTRIBUTES struct. So in C# you need to pass your struct instance (value type) by reference.
Consider using the following prototype instead:
[DllImport("user32", EntryPoint = "CreateDesktopW", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr CreateDesktop(string lpszDesktop, IntPtr lpszDevice, IntPtr pDevmode, int dwFlags,
int dwDesiredAccess, IntPtr lpsa);
Then to call it just create a pinned handle:
GCHandle handle = GCHandle.Alloc(myStruct);
try {
IntPtr pinnedAddress = handle.AddrOfPinnedObject();
}
finally {
handle.Free();
}
This works VERY well for calling PInvoke'd methods with structs.
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.
_GetCardStatus#28 gave me an idea. Unless you are running on 64-bit Windows, you've got the number of arguments wrong. Your P/Invoke for GetCardStatus would be _GetCardStatus#20, because it has 5 32-bit arguments. Your C declaration of GetCardStatus seems to accept the cardId by value rather than by reference. Since CARDIDTYPE is 12 bytes long, this would give the correct length of the argument list (28). Moreover, this would explain both your receiving an error code of -1073741737 (C0000057, STATUS_INVALID_PARAMETER) — since you're not passing a valid cardId — and the access violation — GetCardStatus tries to write to pcbNeeded, which is garbage because the marshaler hasn't even pushed it!
Ergo:
[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) ;
Note reverse order of the three CARDIDTYPE members: stdcall pushes the parameters left-to-right (i.e. towards lower addresses), and my guess is that a struct is "pushed" as a unit.
Also, if you later close the printer handle with CloseHandle, I'd suggest receiving the handle in CARDIDTYPE into an appropriate SafeHandle, not into a bare IntPtr, and declaring GetCardStatus to receive the safe handle.
As Anton suggests the problem lies in the parameters passed to the function. I did not notice this yesterday but the CARDIDTYPE structure is passed by pointer in the GetCardID function, and by value in the GetCardStatus function. In my calls I passed the CARDIDTYPE by pointer to the GetCardStatus also, forcing the P/Invoke framework to locate the correct function by specifying the exact function name as found in Dependecy Walker.
I solved this by defining the CARDIDTYPE as a struct instead of a class, and pass it by reference to the GetCardId function. Further the CARDIDTYPE is marshaled as a Struct when passed to the GetCardStatus function. This in addition to Antons technique of using two function definitions with different pData types (CARD_INFO_1 and CARD_INFO_2) now works correctly. Here is the final definitions:
[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);
Thank you both for your contribution to solving this problem :-)
The problem is you are using [Out] where you should be using nothing
[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);
The Out/In attributes tell the CLR Marshaller in which direction the immediate variable will be marshalled. In the case of the byte[], the parameter is really doing nothing. One of it's sub elements is being moved.
Marshalling arrays is a tricky business though, especially when used directly in a signature vs. a structure. It may be better for you to use an IntPtr, allocate memory there and manually marshal the array out of the 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);
}