Проблема со структурой C# P/Invoke

Я пытаюсь написать оболочку С# P/Invoke для C API (собственная Win dll), и в целом это работает нормально. Единственным исключением является конкретный метод, который принимает структуру в качестве параметра в коде 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);

Затем я пытаюсь вызвать метод EncodeMagstripe, используя следующий код C#:

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, что, согласно документации, означает «Данные содержат слишком много символов для выбранного формата дорожки 2». Однако выбранный формат дорожки должен содержать до 39 символов (я пробовал и более короткие строки).

Я подозреваю, что проблема возникает из-за того, что я сделал что-то неправильно в определении MSTrackData, но я не вижу, что это может быть. У кого-нибудь есть предложения?


person Johnny Egeland    schedule 17.03.2009    source источник


Ответы (4)


Все ответы, данные до сих пор, содержат немного ответа, но являются неполными. Вам нужен MarshalAs - ByValArray, а также новый, ваши MSTrackDatas уже являются ссылками, поэтому вам не нужно передавать их по ссылке, и вы должны проверить, какое соглашение о вызовах представляет ICEAPI, если это StdCall, вам не нужно ничего менять, кроме если это cdecl, вам нужно будет добавить CallingConvention в свой атрибут DllImport. Кроме того, вам может потребоваться добавить атрибут MarshalAs к возвращаемому логическому значению, чтобы убедиться, что оно маршалируется как 4-байтовое логическое значение в стиле WinApi. Вот объявления, которые вам (вероятно) понадобятся:

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);
person Stephen Martin    schedule 20.03.2009
comment
ICEAPI относится к WINAPI, поэтому я также установил CallingConvention = CallingConvention.Winapi в атрибуте DllImport. После реализации вашего предложения звонок сработал отлично :-) Большое спасибо :-) - person Johnny Egeland; 25.03.2009

Я бы определил массив BYTE не с помощью new, а вместо этого использовал следующий код для инициализации правильного размера:

[MarshalAs(UnmanagedType.byValTSt, SizeConst = 256)] открытый только для чтения Byte[] TrackData;

Я успешно использовал это в массивах символов в прошлом.

person weismat    schedule 17.03.2009
comment
Поскольку это массив байтов, а не строка, он, вероятно, должен быть [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]. Однако это не имеет никакого значения, и я все еще получаю ту же ошибку. - person Johnny Egeland; 17.03.2009
comment
OregonGhost прав - не уверен, что это имеет значение - не могу проверить это здесь... - person weismat; 17.03.2009
comment
Проверьте свое соглашение о вызовах - возможно, оно тоже не подходит. - person weismat; 17.03.2009

Мне кажется, проблема в том, что вы передаете ссылку по ссылке. Поскольку MSTrackData — это класс (т. е. ссылочный тип), передача его по ссылке аналогична передаче указателя на указатель.

Измените управляемый прототип на:

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

См. статью MSDN о передаче структур.

person Jim Mischel    schedule 17.03.2009
comment
Да, хорошо, что большинство маршаллингов автоматические. Но Джонни также необходимо передать NULL для 1 из MsTrackData. - person Henk Holterman; 17.03.2009
comment
А так как MSTrackData является ссылочным типом в его примере, передать значение null несложно. - person Jim Mischel; 18.03.2009

У меня была почти точно такая же проблема, но с 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);
        }
    }
person Evgeny    schedule 31.07.2009
comment
Мое вышеприведенное решение отлично сработает для ReadMagstripe, но, поскольку эта функция возвращает данные в структурах MSTrackData, атрибут [In] в каждом из параметров MSTrackData необходимо изменить на [In, Out]. - person Stephen Martin; 31.07.2009
comment
О, понятно... Я точно не знал, для чего нужны атрибуты In. - person Evgeny; 01.08.2009