2009-03-17 21 views
6

Estoy tratando de escribir un contenedor C# P/Invoke para una API C (una DLL nativa Win), y generalmente esto está funcionando bien. La única excepción es un método específico que toma una estructura como parámetro en el código C. La función se invoca sin excepciones, pero devuelve falso indicando que algo falló en la ejecución.C# P/Problema de estructura de invocación

En la cabecera API presentar el método y estructuras implicados se definen como sigue:

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

he hecho un intento de crear un C# P/Invoke envoltura mediante el siguiente código:

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

entonces trato de invocar el método EncodeMagstripe usando el siguiente código 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 provoca una excepción ApplicationException, y el código de error es 801, que según la documentación significa "Los datos incluyen demasiados caracteres para el formato Track 2 seleccionado". Sin embargo, el formato de pista seleccionado debe permitir hasta 39 caracteres (también he probado cadenas más cortas).

Sospecho que el problema se debe a algo que hice mal en la definición de MSTrackData, pero no puedo ver lo que puede ser. ¿Alguien tiene alguna sugerencia?

Respuesta

5

Todas las respuestas dadas hasta ahora tienen un poco de respuesta pero están incompletas. Necesita MarshalAs - ByValArray así como también los nuevos, sus MSTrackDatas ya son referencias, por lo que no necesita pasarlos por ref y debe verificar qué convención de llamadas ICEAPI representa, si es StdCall no necesita cambiar nada más que si es cdecl, necesitará agregar CallingConvention a su atributo DllImport. Además, es posible que deba agregar un atributo MarshalAs a su valor de retorno bool para asegurarse de que se clasifique como bool estilo WinApi de 4 bytes. Aquí están los declara que usted (probablemente) necesita:

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

ICEAPI se refiere a WINAPI, por lo que también configuro "CallingConvention = CallingConvention.Winapi" en el atributo DllImport. Después de implementar su sugerencia, la llamada funcionó perfectamente :-) Muchas gracias :-) –

2

Yo definiría la matriz de bytes no con la nueva, pero utilizar el siguiente código en vez para inicializar el tamaño correcto:

[MarshalAs (UnmanagedType.byValTSt, SizeConst = 256)] Byte público de sólo lectura [] TrackData;

He utilizado esto con éxito en matrices char en el pasado.

+0

¿No debería ser UnmanagedType.ByValArray? – OregonGhost

+0

Dado que esto es una matriz de bytes y no una cadena, probablemente debería ser [MarshalAs (UnmanagedType.ByValArray, SizeConst = 256)] Sin embargo, esto no hizo ninguna diferencia, y sigo recibiendo el mismo error. –

+0

OregonGhost está en lo cierto, aunque no está seguro si hace la diferencia, no puede probarlo aquí ... – weismat

1

Me parece que el problema es que está pasando una referencia por referencia. Como MSTrackData es una clase (es decir, tipo de referencia), pasarla por referencia es como pasar un puntero a puntero.

Cambiar la prototipo conseguido:

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

vea el artículo de MSDN sobre passing structures.

+0

Sí, es bueno cómo la mayoría de Marshalling es automático. Pero Johnny también necesita pasar NULL para 1 de los MsTrackData. –

+0

Y dado que MSTrackData es un tipo de referencia en su ejemplo, es trivial pasar el nulo. –

0

que tenían casi exactamente el mismo problema - pero con ReadMagstripe. ¡Y la solución provista aquí para EncodeMagstripe no funcionó para ReadMagstripe! Creo que la razón por la que no funcionó fue porque ReadMagstripe tiene que devolver datos a la estructura/clase TRACKDATA, mientras que EncodeMagstripe solo pasa datos a la dll y los datos en TRACKDATA no necesitan ser cambiados.Aquí está la implementación que finalmente funcionó para mí, ambas con EncodeMagstripe y 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

Mi solución anterior funcionaría bien para ReadMagstripe pero como esta función devuelve datos en MSTrackData estructura el atributo [In] en cada uno de los MSTrackData los parámetros tendrían que cambiarse a [In, Out]. –

+0

Oh, ya veo ... No sabía exactamente cuál es el propósito de In Attrubutes. – Evgeny