Reputation: 567
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?
Upvotes: 6
Views: 6689
Reputation: 3332
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);
}
}
Upvotes: 0
Reputation: 9645
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);
Upvotes: 5
Reputation: 134125
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.
Upvotes: 1
Reputation: 7411
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.
Upvotes: 2