2009-03-17 8 views
6

Sto provando a scrivere un wrapper C# P/Invoke per un'API C (una dll nativa per Win), e in genere funziona correttamente. L'unica eccezione è un metodo specifico che prende una struct come parametro nel codice C. La funzione è invocata senza eccezioni, ma restituisce false indicando che qualcosa non è riuscito nell'esecuzione.C# P/Invoke structure problem

Nell'intestazione API depositare il metodo coinvolti e struct sono definiti come segue:

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

Ho fatto un tentativo di creare un C# P/Invoke involucro utilizzando il codice seguente:

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

Poi cerco di richiamare il metodo EncodeMagstripe utilizzando il seguente codice C#:

CardApi.MSTrackData trackNull = null; 
CardApi.MSTrackData track2 = new CardApi.TrackData(); 
byte[] trackBytes = Encoding.ASCII.GetBytes(";?"); 
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()); 
} 

Th fa sì che venga lanciata una ApplicationException e il codice di errore è 801 che, secondo la documentazione, significa che "Dati include troppi caratteri per il formato di traccia 2 selezionato". Tuttavia il formato di traccia selezionato dovrebbe consentire fino a 39 caratteri (ho anche provato stringhe più corte).

Sospetto che il problema si verifichi a causa di qualcosa che ho sbagliato nella definizione di MSTrackData, ma non riesco a vedere cosa possa essere. Qualcuno ha qualche suggerimento?

risposta

5

Tutte le risposte fornite finora hanno un po 'di risposta, ma sono incomplete. Hai bisogno di MarshalAs - ByValArray così come del nuovo, i tuoi MSTrackDatas sono già riferimenti quindi non è necessario passarli per ref e devi verificare quale convenzione di chiamata rappresenta ICEAPI, se è StdCall non è necessario modificare nulla ma se è cdecl sarà necessario aggiungere CallingConvention all'attributo DllImport. Inoltre, potrebbe essere necessario aggiungere un attributo MarshalAs al valore di ritorno bool per assicurarsi che venga eseguito il marshalling come bool di stile WinApi a 4 byte. Ecco le dichiara avrete (probabilmente) necessario:

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

L'ICEAPI fa riferimento a WINAPI, quindi ho anche impostato "CallingConvention = CallingConvention.Winapi" nell'attributo DllImport. Dopo aver implementato il vostro suggerimento, la chiamata ha funzionato perfettamente :-) Grazie mille :-) –

2

avrei definire la matrice BYTE non con nuovo, ma utilizzare invece il seguente codice per inizializzare il giusto formato:

[MarshalAs (UnmanagedType.byValTSt, SizeConst = 256)] pubblico Byte sola lettura [] TrackData;

Ho usato questo con successo su matrici di carbone in passato.

+0

Che non dovrebbe essere UnmanagedType.ByValArray? – OregonGhost

+0

Poiché si tratta di un array di byte e non una stringa, probabilmente dovrebbe essere [MarshalAs (UnmanagedType.ByValArray, SizeConst = 256)] Tuttavia, questo non ha fatto alcuna differenza, e ho ancora ottenere lo stesso errore. –

+0

OregonGhost è giusto - non so se si fa la differenza anche se - non si può provare qui ... – weismat

1

Mi sembra che il problema sia che stai passando un riferimento per riferimento. Poiché MSTrackData è una classe (ovvero un tipo di riferimento), passarla per riferimento è come passare un puntatore al puntatore.

Cambia la tua prototipo riuscito a:

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

vedere l'articolo MSDN su passing structures.

+0

Sì, bello come la maggior parte del Marshalling è automatico. Ma Johnny ha anche bisogno di passare NULL per 1 dei MsTrackData. –

+0

E poiché MSTrackData è un tipo di riferimento nel suo esempio, è banale passare il null. –

0

avevo quasi esattamente lo stesso problema - ma con ReadMagstripe. E la soluzione fornita qui per EncodeMagstripe non ha funzionato per ReadMagstripe! Penso che la ragione per cui non ha funzionato è che ReadMagstripe deve restituire i dati nella struttura/classe TRACKDATA, mentre EncodeMagstripe passa solo i dati alla DLL e i dati in TRACKDATA non devono essere modificati.Ecco l'implementazione che alla fine ha funzionato per me - entrambi con EncodeMagstripe e 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="[email protected]", 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="[email protected]", 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); 
     } 
    } 
+1

mio sopra soluzione potrebbe funzionare bene per ReadMagstripe ma poiché questa funzione restituisce i dati nelle strutture MSTrackData [IN] attributo su ciascuno dei MSTrackData i parametri dovrebbero essere cambiati in [In, Out]. –

+0

Oh capisco ... Non sapevo esattamente quale sia lo scopo degli In attrubutes. – Evgeny