2012-04-25 8 views
18

Por lo tanto, ha habido muchas variantes de esta pregunta, y después de ver varias todavía no puedo entenderlo.C# llamando a la función C que devuelve struct con arreglo de caracteres de tamaño fijo

Este es el código C:

typedef struct 
{ 
unsigned long Identifier; 
char Name[128]; 
} Frame; 

Frame GetFrame(int index); 

Este es el código C#:

struct Frame 
{ 
    public ulong Identifier; 
    [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I1, SizeConst = 128)] 
    public char[] Name; 
} 

[DllImport("XNETDB.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 
private static extern Frame GetFrame(int index); 

Este es el último intento he intentado en C#, y parece bastante lógico, pero me da la error "La firma del método no es compatible con Invoke". Por lo tanto, estoy perdido en qué probar a continuación. Cualquier ayuda es apreciada.

Gracias, Kevin

ActualizadoKevin añadió esto como una edición de mi respuesta

Debería vez cambiar mi código C:

void GetFrame(int index, Frame * f); 

y usar en su lugar para C# :

struct Frame 
{ 
    public uint Identifier; 
    [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 128)] 
    public string Name; 
} 

[DllImport("XNETDB.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 
private static extern void GetFrame(int index, ref Frame f); 
+1

¿Has visto este http://social.msdn.microsoft.com/Forums/en-AU/csharplanguage/thread/6e0ad208-5c8b-48ac-a45e-cfaf7f52221b? – MilkyWayJoe

+0

La definición de la función existe. El código C listado solo es del archivo de encabezado. –

+0

Vi eso y probé el intPtr GetFrame externo estático (índice int); pero al llamar eso arroja el error "Intentó leer o escribir en la memoria protegida". –

Respuesta

8

El problema es que la función nativa devuelve un tipo no blittable como valor de retorno.

http://msdn.microsoft.com/en-us/library/ef4c3t39.aspx

P/Invoke no puede tener tipos no blittable como un valor de retorno.

No puede p/invocar ese método. [EDITAR En realidad, es posible, ver JaredPar's answer]

Volviendo 132 bytes por valor es una mala idea. Si este código nativo es tuyo, lo arreglaría. Puede solucionarlo asignando los 132 bytes y devolviendo un puntero. A continuación, agregue un método FreeFrame para liberar esa memoria. Ahora puede ser p/invocado.

Alternativamente, podría cambiar para que acepte un puntero a la memoria de trama que se va a rellenar.

+0

+1, se olvidó por completo del tipo de devolución debe ser regla blittable. Acepte que si tiene el control, es mejor proporcionar una capa más fácil. Si no, agregué una solución viable (pero fea). – JaredPar

+0

@ kevin.key Marcaría la respuesta de JaredPar como la respuesta ahora, ya que funciona directamente si no puede cambiar el código nativo. – Tergiver

+0

Lo marqué como la respuesta. La clave estaba pasando la estructura por ref en C# y cambiando la función C para pasar la estructura a través de un parámetro de puntero. –

20

Hay dos problemas con la firma PInvoke que ha elegido.

El primero es fácil de arreglar. Tiene una traducción incorrecta de unsigned long. En C, un unsigned long suele tener solo 4 bytes. Eligió el tipo de C# long que tiene 8 bytes. Cambiar el código C# para usar uint lo arreglará.

El segundo es un poco más difícil. Como Tergiver señaló, el CLR Marshaller solo admite una estructura en la posición de retorno si es blittable. Blittable es una forma elegante de decir que tiene exactamente la misma representación de memoria en código nativo y administrado. La definición de estructura que ha elegido no es blittable porque tiene una matriz anidada.

Esto se puede solucionar, aunque recuerde que PInvoke es un proceso muy simple. El CLR Marshaller realmente solo necesita que responda 2 preguntas con la firma de sus tipos y métodos de pinvoke

  • ¿Cuántos bytes estoy copiando?
  • ¿En qué dirección deben ir?

En este caso, el número de bytes es sizeof(unsigned long) + 128 == 132. Entonces, todo lo que tenemos que hacer es crear un tipo administrado que sea blittable y tenga un tamaño de 132 bytes. La forma más sencilla de hacer esto es definir una mancha para manejar la parte de conjunto de

[StructLayout(LayoutKind.Sequential, Size = 128)] 
struct Blob 
{ 
    // Intentionally left empty. It's just a blob 
} 

Ésta es una estructura que no tiene miembros que aparecerán al contador de referencias que tienen un tamaño de 128 bytes (y como un bono que es blittable !). Ahora podemos definir fácilmente la estructura Frame como una combinación de un uint y este tipo

struct Frame 
{ 
    public int Identifier; 
    public Blob NameBlob; 
    ... 
} 

Ahora tenemos una tipo blittable con un tamaño del marshaller verá como 132 bytes.Esto significa que funcionará perfectamente con la firma GetFrame que ha definido

La única parte restante le da acceso al char[] real para el nombre. Esto es un poco complicado, pero se puede resolver con un poco de magia mariscal.

public string GetName() 
{ 
    IntPtr ptr = IntPtr.Zero; 
    try 
    { 
     ptr = Marshal.AllocHGlobal(128); 
     Marshal.StructureToPtr(NameBlob, ptr, false); 
     return Marshal.PtrToStringAnsi(ptr, 128); 
    } 
    finally 
    { 
     if (ptr != IntPtr.Zero) 
     { 
      Marshal.FreeHGlobal(ptr); 
     } 
    } 
} 

Nota: No puedo comentar sobre la porción convención de llamada porque estoy familiarizado con la API GetFrame pero eso es algo que definitivamente comprobar.

+0

No puedo ver que el tipo sea más o menos compatible con P/Invoke en función de este cambio, aunque ciertamente es un problema de portabilidad que debe corregirse. –

+1

@BenVoigt puede causar una gran diferencia. En la firma original, el marshaller asumirá que 'char []' comienza en el desplazamiento 8 desde el comienzo de la estructura frente al valor real de 4. Como resultado, el marcador de marshal ve 4 bytes que realmente no están allí. Escribirá en estos 4 bytes cuando se ordene a nativo y leerá estos 4 bytes cuando se vuelva a generar desde nativo. Los 4 bytes son esencialmente basura y, por lo tanto, conducen a un comportamiento indefinido – JaredPar

+0

Por supuesto, afecta la corrección. Pero no causará un error de compilación. Ambos 'ulong' y' uint' son blittables, p/invoke los maneja de la misma manera (aunque en diferente medida). –

3

Otra opción para JaredPar de es utilizar la función de búfer de tamaño fijo de C#. Sin embargo, esto requiere que active la configuración para permitir el código inseguro, pero evita tener 2 estructuras.

class Program 
{ 
    private const int SIZE = 128; 

    unsafe public struct Frame 
    { 
     public uint Identifier; 
     public fixed byte Name[SIZE]; 
    } 

    [DllImport("PinvokeTest2.DLL", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 
    private static extern Frame GetFrame(int index); 

    static unsafe string GetNameFromFrame(Frame frame) 
    { 
     //Option 1: Use if the string in the buffer is always null terminated 
     //return Marshal.PtrToStringAnsi(new IntPtr(frame.Name)); 

     //Option 2: Use if the string might not be null terminated for any reason, 
     //like if were 128 non-null characters, or the buffer has corrupt data. 

     return Marshal.PtrToStringAnsi(new IntPtr(frame.Name), SIZE).Split('\0')[0]; 
    } 

    static void Main() 
    { 
     Frame a = GetFrame(0); 
     Console.WriteLine(GetNameFromFrame(a)); 
    } 
} 
+1

¡Bonito! Puede usar la sobrecarga 'PtrToStringAnsi' con un tamaño de 128 para que no se sobrepase si no hay un carácter NULL de terminación. – Tergiver

+0

Si hay alguna posibilidad de que no haya un carácter nulo, entonces debes usar la segunda opción que muestro, que usa esa sobrecarga. Desafortunadamente AFAICT que la sobrecarga siempre creará una cadena de 128 caracteres, en lugar de detenerse en el primer NULL. Es por eso que agregué la división en el carácter nulo, y seleccionando el primer resultado. –

+0

Tiene razón, la longitud resultante de la cuerda será el tamaño que pase. – Tergiver

Cuestiones relacionadas