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.
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
@vladimir: ¿estás usando 'UnmanagedType.LPTStr'?Además: intente especificar el 'CharSet' como sugirieron los otros. – fretje
"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 –