2009-11-17 30 views
5

En mi código C# estoy tratando de obtener una matriz de estructuras de una DLL heredada de C++ (el código que no puedo cambiar).¿Cómo ordenar una serie de estructuras de C++ a C#?

En ese código C++, la estructura se define así:

struct MyStruct 
{ 
    char* id; 
    char* description; 
}; 

El método que estoy llamando (get_my_structures) devuelve un puntero a una matriz de estructuras MyStruct:

MyStruct* get_my_structures() 
{ 
    ... 
} 

Hay otro método que devuelve el número de estructuras, así que sé cuántas estructuras devuelven. MyStruct

En mi código C#, he definido así:

[StructLayout(LayoutKind.Sequential)] 
public class MyStruct 
{ 
    [MarshalAsAttribute(UnmanagedType.LPStr)] // <-- also tried without this 
    private string _id; 
    [MarshalAsAttribute(UnmanagedType.LPStr)] 
    private string _description; 
} 

La firma de interoperabilidad se ve así:

[DllImport("legacy.dll", EntryPoint="get_my_structures")] 
public static extern IntPtr GetMyStructures(); 

Finalmente, el código que obtiene la matriz de estructuras myStruct parece esto:

int structuresCount = ...; 
IntPtr myStructs = GetMyStructures(); 
int structSize = Marshal.SizeOf(typeof(MyStruct)); // <- returns 8 in my case 
for (int i = 0; i < structuresCount; i++) 
{ 
    IntPtr data = new IntPtr(myStructs.ToInt64() + structSize * i); 
    MyStruct ms = (MyStruct) Marshal.PtrToStructure(data, typeof(MyStruct)); 
    ... 
} 

El problema es que solo la primera estructura (una en el o ffset zero) se clasifica correctamente. Los siguientes tienen valores falsos en los miembros _id y _description. Los valores no están completamente destrozados, o eso parece: son cadenas de otras ubicaciones de memoria. El código en sí no falla.

He verificado que el código C++ en get_my_structures() devuelve datos correctos. Los datos no se borran o modifican accidentalmente durante o después de la llamada.

Visto en un depurador, C++ distribución de la memoria de los datos devueltos se parece a esto:

0: id (char*)   <---- [MyStruct 1] 
4: description (char*) 
8: id (char*)   <---- [MyStruct 2] 
12: description (char*) 
16: id (char*)   <---- [MyStruct 3] 
... 

[Actualización 18/11/2009]

Así es como el código C++ prepara estas estructuras (el código real es mucho más feo, pero esto es una aproximación suficientemente cerca):

static char buffer[12345] = {0}; 
MyStruct* myStructs = (MyStruct*) &buffer; 
for (int i = 0; i < structuresCount; i++) 
{ 
    MyStruct* ms = <some other permanent address where the struct is>; 
    myStructs[i].id = (char*) ms->id; 
    myStructs[i].description = (char*) ms->description; 
} 
return myStructs; 

es cierto que el código anterior hace algunos casting feo y copia punteros sin procesar, pero parece hacer eso correctamente. Al menos eso es lo que veo en el depurador: el buffer anterior (estático) contiene todos estos punteros char * desnudos almacenados uno tras otro, y apuntan a ubicaciones válidas (no locales) en la memoria.

El ejemplo de Pavel muestra que este es realmente el único lugar donde las cosas pueden salir mal. Trataré de analizar qué sucede con esas ubicaciones 'finales' donde realmente están las cadenas, no las ubicaciones donde se almacenan los punteros.

Respuesta

0

Tienes que usar UnmanagedType.LPTStr para char *. También un StringBuilder se recomienda para un const char * no: y una especificación CharSet:

[StructLayout(LayoutKind.Sequential, Charset = CharSet.Auto)] 
public class MyStruct 
{ 
    [MarshalAsAttribute(UnmanagedType.LPTStr)] 
    private StringBuilder _id; 
    [MarshalAsAttribute(UnmanagedType.LPTStr)] 
    private StringBuilder _description; 
} 

En cuanto a la declaración DllImport, ¿ha intentado

[DllImport("legacy.dll", EntryPoint="get_my_structures")] 
public static extern MarshalAs(UnmanagedType.LPArray) MyStruct[] GetMyStructures(); 

?

Además, si el anterior no funciona, dejarlo en IntPtr y tratar de Mashal las estructuras devueltos así:

for (int i = 0; i < structuresCount; i++) 
{ 
    MyStruct ms = (MyStruct) Marshal.PtrToStructure(myStructs, typeof(MyStruct)); 
    ... 
    myStructs += Marshal.SizeOf(ms); 
} 
+0

Si trato de usar un StringBuilder obtengo una ArgumentException cuando trato de hacer esto: int itemSize = Marshal.SizeOf (typeof (MyStruct)); El mensaje de error es "Tipo 'MyStruct' no se puede ordenar como una estructura no administrada, no se puede calcular ningún tamaño significativo o desplazamiento". – vladimir

+0

@vladimir: ¿estás usando 'UnmanagedType.LPTStr'?Además: intente especificar el 'CharSet' como sugirieron los otros. – fretje

+0

"Las cadenas son miembros válidos de las estructuras; sin embargo, los almacenamientos intermedios de StringBuilder no son válidos en las estructuras". -> http://msdn.microsoft.com/en-us/library/s9ts558h.aspx#Mtps_DropDownFilterText –

0

por lo general termino estas cosas que se resuelve por ensayo y error. Asegúrate de tener la propiedad CharSet configurada en tu StructLayout, y probaría UnmanagedType.LPTStr, parece funcionar mejor para char *, aunque no estoy seguro de por qué.

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)] 
public class MyStruct 
{ 
    [MarshalAsAttribute(UnmanagedType.LPTStr)] 
    private string _id; 
    [MarshalAsAttribute(UnmanagedType.LPTStr)] 
    private string _description; 
} 
0

creo, también, además de las respuestas dadas, que tiene que suministrar la longitud, así, es decir, [MarshalAsAttribute (UnmanagedType.LPTStr), SizeConst =, = ArraySubType System.Runtime.InteropServices. UnmanagedType.AnsiBStr)]

Esto es una prueba y error para hacer esto bien, también, otra cosa a considerar, en algunas llamadas WinAPI que esperan un parámetro de cadena, generalmente un parámetro ref, puede valer la pena intentarlo la clase StringBuilder también ... Nada más viene a la mente en esto aparte de los puntos que he mencionado aquí ... Espero que esto ayude, Tom

+0

char * es nulo terminado, por lo que no proporcionaría la longitud. Es por eso que tenemos UnmanagedType.LPWStr y UnmanagedType.LPTStr. Si tuvieras que proporcionar la longitud, también tendrías que hacerlo realmente grande para no truncar ningún dato. No es una solución elegante. – ParmesanCodice

1

Cambiaría la estructura. En lugar de cuerdas, etc., utilice IntPtr:

 
[StructLayout(LayoutKind.Sequential)] 
public class MyStruct 
{ 
    private IntPtr _id; 
    private IntPtr _description; 
} 

Entonces cada valor de la serie C# cabría contar manualmente a cadena mediante Marshal.PtrToString teniendo en cuenta charset etc.

+0

Suena como un enfoque viable, pero desafortunadamente no parece resolver el problema que tengo. Incluso con los miembros de IntPtr, esos miembros aún tienen los mismos valores (inválidos) en todas las estructuras que no sean la primera. – vladimir

3

No puedo reproducir el problema, lo cual me lleva a sospechar que realmente está en el lado C++ de las cosas. Aquí está el código fuente completo para mi intento.

dll.cpp - compilar con cl.exe /LD:

extern "C" { 

struct MyStruct 
{ 
    char* id; 
    char* description; 
}; 

__declspec(dllexport) 
MyStruct* __stdcall get_my_structures() 
{ 
    static MyStruct a[] = 
    { 
     { "id1", "desc1" }, 
     { "id2", "desc2" }, 
     { "id3", "desc3" } 
    }; 
    return a; 

} 

} 

test.cs - compilar con csc.exe /platform:x86:

using System; 
using System.Runtime.InteropServices; 


[StructLayout(LayoutKind.Sequential)] 
public class MyStruct 
{ 
    [MarshalAsAttribute(UnmanagedType.LPStr)] 
    public string _id; 
    [MarshalAsAttribute(UnmanagedType.LPStr)] 
    public string _description; 
} 


class Program 
{ 
    [DllImport("dll")] 
    static extern IntPtr get_my_structures(); 

    static void Main() 
    { 
     int structSize = Marshal.SizeOf(typeof(MyStruct)); 
     Console.WriteLine(structSize); 

     IntPtr myStructs = get_my_structures(); 
     for (int i = 0; i < 3; ++i) 
     { 
      IntPtr data = new IntPtr(myStructs.ToInt64() + structSize * i); 
      MyStruct ms = (MyStruct) Marshal.PtrToStructure(data, typeof(MyStruct)); 

      Console.WriteLine(); 
      Console.WriteLine(ms._id); 
      Console.WriteLine(ms._description); 
     } 
    } 
} 

Esto imprime correctamente a las 3 estructuras.

¿Puedes mostrar tu código en C++ que llena las estructuras? El hecho de que pueda llamarlo directamente desde C++ y obtener los resultados correctos no significa necesariamente que sea correcto. Por ejemplo, podría devolver un puntero a una estructura asignada por la pila. Al hacer una llamada directa, obtendría un puntero técnicamente no válido, pero los datos probablemente se mantendrían. Al realizar la ordenación de P/Invoke, las estructuras de datos de P/Invoke pueden sobrescribir la pila según el punto donde intente leer los valores desde allí.

+0

He actualizado mi pregunta, agregando el código C++. Como ha demostrado que no hay nada de malo con la parte de interoperabilidad propiamente dicha (ni en el lado C#), de hecho es lógico concluir que debe ser el código C++. – vladimir

Cuestiones relacionadas