C#P / Invoke结构问题

时间:2009-03-17 15:25:39

标签: c# struct pinvoke

我正在尝试为C API(本机Win dll)编写C#P / Invoke包装器,通常这很正常。唯一的例外是将结构作为C代码中的参数的特定方法。该函数在没有任何异常的情况下被调用,但它返回false,表示执行中的某些内容失败。

在API头文件中,涉及的方法和结构定义如下:

#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*/);

我尝试使用以下代码创建C#P / Invoke包装器:

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);

然后我尝试使用以下C#代码调用EncodeMagstripe方法:

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());
}

这会导致抛出ApplicationException,错误代码为801,根据文档的含义,“数据包含所选Track 2格式的太多字符”。但是,所选的曲目格式最多允许39个字符(我也尝试过较短的字符串)。

我怀疑问题是由于我在MSTrackData定义中做错了,但我看不出这可能是什么。有没有人有任何建议?

4 个答案:

答案 0 :(得分:5)

到目前为止给出的所有答案都有一些答案,但不完整。你需要MarshalAs - ByValArray以及新的,你的MSTrackDatas已经是引用所以你不需要通过ref传递它们你必须检查ICEAPI代表什么样的调用约定,如果它是StdCall你不需要改变任何东西但是如果是cdecl,则需要将CallingConvention添加到DllImport属性中。此外,您可能需要将MarshalAs属性添加到bool返回值,以确保它被封送为4字节WinApi样式bool。以下是您(可能)需要的声明:

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);

答案 1 :(得分:2)

我将使用new定义BYTE数组,但使用以下代码来初始化正确的大小:

  

[MarshalAs(UnmanagedType.byValTSt,SizeConst = 256)]   public readonly Byte [] TrackData;

我过去在char数组上成功使用过它。

答案 2 :(得分:1)

在我看来,问题在于您是通过引用传递引用。由于MSTrackData是一个类(即引用类型),因此通过引用传递它就像传递一个指向指针一样。

将您的托管原型更改为:

public static extern bool EncodeMagstripe(IntPtr hDC,
                    MSTrackData pTrack1,
                    MSTrackData pTrack2,
                    MSTrackData pTrack3,
                    MSTrackData reserved);

请参阅有关passing structures的MSDN文章。

答案 3 :(得分:0)

我有几乎完全相同的问题 - 但是使用了ReadMagstripe。这里为EncodeMagstripe提供的解决方案对ReadMagstripe不起作用!我认为它不起作用的原因是ReadMagstripe必须将数据返回到TRACKDATA结构/类,而EncodeMagstripe只将数据传递给dll,而TRACKDATA中的数据不需要更改。以下是最终适用于我的实现 - 包括EncodeMagstripe和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);
        }
    }