¿Cómo clasifico una estructura que contiene una matriz de tamaño variable a C #?

¿Cómo clasifico este tipo de C ++?

La estructura ABS_DATA se usa para asociar un bloque de datos arbitrariamente largo con la información de longitud. La longitud declarada de la matriz de Data es 1, pero la longitud real está dada por el miembro de Length .

 typedef struct abs_data { ABS_DWORD Length; ABS_BYTE Data[ABS_VARLEN]; } ABS_DATA; 

Probé el siguiente código, pero no funciona. La variable de datos siempre está vacía y estoy seguro de que tiene datos allí.

 [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet = System.Runtime.InteropServices.CharSet.Ansi)] public struct abs_data { /// ABS_DWORD->unsigned int public uint Length; /// ABS_BYTE[1] [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 1)] public string Data; } 

Una vieja pregunta, pero recientemente tuve que hacer esto yo mismo y todas las respuestas existentes son malas, así que …

La mejor solución para ordenar una matriz de longitud variable en una estructura es usar un contador de referencias personalizado . Esto le permite controlar el código que el tiempo de ejecución utiliza para convertir datos gestionados y no gestionados. Desafortunadamente, la clasificación personalizada está mal documentada y tiene algunas limitaciones extrañas. Los cubriré rápidamente, luego repasaré la solución.

Es molesto que no puedas usar el cálculo de referencias personalizado en un elemento de matriz de una estructura o clase. No existe una razón documentada o lógica para esta limitación, y el comstackdor no se quejará, pero obtendrá una excepción en tiempo de ejecución. Además, hay una función que los evaluadores personalizados deben implementar, int GetNativeDataSize() , que obviamente es imposible de implementar con precisión (no le pasa una instancia del objeto para preguntar su tamaño, por lo que solo puede desactivar el tipo, que ¡es, por supuesto, de tamaño variable!) Afortunadamente, esta función no tiene importancia. Nunca he visto que se llame, y el marcador personal funciona bien incluso si devuelve un valor falso (un ejemplo de MSDN lo ha devuelto -1).

Primero que nada, aquí está lo que creo que podría ser su prototipo nativo (estoy usando P / Invocar aquí, pero también funciona para COM):

 // Unmanaged C/C++ code prototype (guess) //void DoThing (ABS_DATA *pData); // Guess at your managed call with the "marshal one-byte ByValArray" version //[DllImport("libname.dll")] public extern void DoThing (ref abs_data pData); 

Aquí está la versión ingenua de cómo podrías haber usado un marcador personalizado (que realmente debería haber funcionado). Iré al marshaler en un momento …

 [StructLayout(LayoutKind.Sequential)] public struct abs_data { // Don't need the length as a separate filed; managed arrays know it. [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(ArrayMarshaler))] public byte[] Data; } // Now you can just pass the struct but it takes arbitrary sizes! [DllImport("libname.dll")] public extern void DoThing (ref abs_data pData); 

Desafortunadamente, en tiempo de ejecución, aparentemente no se pueden ordenar matrices dentro de las estructuras de datos como nada excepto SafeArray o ByValArray . SafeArrays se cuentan, pero no se parecen en nada al formato (extremadamente común) que está buscando aquí. Entonces eso no funcionará. ByValArray, por supuesto, requiere que la longitud se conozca en tiempo de comstackción, por lo que tampoco funciona (como te topaste). Curiosamente, sin embargo, puede utilizar cálculo de referencias personalizado en los parámetros de la matriz. Esto es molesto porque debe colocar MarshalAsAttribute en cada parámetro que utiliza este tipo, en lugar de ponerlo en un campo y aplicar eso en todos los lugares donde use el tipo que contiene ese campo, pero c’est la vie. Se parece a esto:

 [StructLayout(LayoutKind.Sequential)] public struct abs_data { // Don't need the length as a separate filed; managed arrays know it. // This isn't an array anymore; we pass an array of this instead. public byte Data; } // Now you pass an arbitrary-sized array of the struct [DllImport("libname.dll")] public extern void DoThing ( // Have to put this huge stupid attribute on every parameter of this type [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(ArrayMarshaler))] // Don't need to use "ref" anymore; arrays are ref types and pass as pointer-to abs_data[] pData); 

En ese ejemplo, abs_data tipo abs_data , en caso de que quiera hacer algo especial con él (constructores, funciones estáticas, propiedades, herencia, lo que sea). Si los elementos de su matriz consistían en un tipo complejo, modificaría la estructura para representar ese tipo complejo. Sin embargo, en este caso, abs_data es básicamente un byte redenominado, ni siquiera está “envolviendo” el byte; en lo que respecta al código nativo, se parece más a un typedef, por lo que puede pasar una matriz de bytes y omitir la estructura por completo:

 // Actually, you can just pass an arbitrary-length byte array! [DllImport("libname.dll")] public extern void DoThing ( // Have to put this huge stupid attribute on every parameter of this type [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(ArrayMarshaler))] byte[] pData); 

Bien, ahora puede ver cómo declarar el tipo de elemento de matriz (si es necesario) y cómo pasar la matriz a una función no administrada. Sin embargo, aún necesitamos ese marcador personalizado. Debería leer ” Implementación de la interfaz ICustomMarshaler “, pero lo trataré aquí, con comentarios en línea. Tenga en cuenta que utilizo algunas convenciones de taquigrafía (como Marshal.SizeOf() ) que requieren .NET 4.5.1 o superior.

 // The class that does the marshaling. Making it generic is not required, but // will make it easier to use the same custom marshaler for multiple array types. public class ArrayMarshaler : ICustomMarshaler { // All custom marshalers require a static factory method with this signature. public static ICustomMarshaler GetInstance (String cookie) { return new ArrayMarshaler(); } // This is the function that builds the managed type - in this case, the managed // array - from a pointer. You can just return null here if only sending the // array as an in-parameter. public Object MarshalNativeToManaged (IntPtr pNativeData) { // First, sanity check... if (IntPtr.Zero == pNativeData) return null; // Start by reading the size of the array ("Length" from your ABS_DATA struct) int length = Marshal.ReadInt32(pNativeData); // Create the managed array that will be returned T[] array = new T[length]; // For efficiency, only compute the element size once int elSiz = Marshal.SizeOf(); // Populate the array for (int i = 0; i < length; i++) { array[i] = Marshal.PtrToStructure(pNativeData + sizeof(int) + (elSiz * i)); } // Alternate method, for arrays of primitive types only: // Marshal.Copy(pNativeData + sizeof(int), array, 0, length); return array; } // This is the function that marshals your managed array to unmanaged memory. // If you only ever marshal the array out, not in, you can return IntPtr.Zero public IntPtr MarshalManagedToNative (Object ManagedObject) { if (null == ManagedObject) return IntPtr.Zero; T[] array = (T[])ManagedObj; int elSiz = Marshal.SizeOf(); // Get the total size of unmanaged memory that is needed (length + elements) int size = sizeof(int) + (elSiz * array.Length); // Allocate unmanaged space. For COM, use Marshal.AllocCoTaskMem instead. IntPtr ptr = Marshal.AllocHGlobal(size); // Write the "Length" field first Marshal.WriteInt32(ptr, array.Length); // Write the array data for (int i = 0; i < array.Length; i++) { // Newly-allocated space has no existing object, so the last param is false Marshal.StructureToPtr(array[i], ptr + sizeof(int) + (elSiz * i), false); } // If you're only using arrays of primitive types, you could use this instead: //Marshal.Copy(array, 0, ptr + sizeof(int), array.Length); return ptr; } // This function is called after completing the call that required marshaling to // unmanaged memory. You should use it to free any unmanaged memory you allocated. // If you never consume unmanaged memory or other resources, do nothing here. public void CleanUpNativeData (IntPtr pNativeData) { // Free the unmanaged memory. Use Marshal.FreeCoTaskMem if using COM. Marshal.FreeHGlobal(pNativeData); } // If, after marshaling from unmanaged to managed, you have anything that needs // to be taken care of when you're done with the object, put it here. Garbage // collection will free the managed object, so I've left this function empty. public void CleanUpManagedData (Object ManagedObj) { } // This function is a lie. It looks like it should be impossible to get the right // value - the whole problem is that the size of each array is variable! // - but in practice the runtime doesn't rely on this and may not even call it. // The MSDN example returns -1; I'll try to be a little more realistic. public int GetNativeDataSize () { return sizeof(int) + Marshal.SizeOf(); } } 

¡Uf, eso fue largo! Bueno, ahí lo tienes. Espero que la gente vea esto, porque hay muchas malas respuestas y malentendidos por ahí …

No es posible ordenar las estructuras que contienen matrices de longitud variable (pero es posible ordenar las matrices de longitud variable como parámetros de función). Deberá leer sus datos manualmente:

 IntPtr nativeData = ... ; var length = Marshal.ReadUInt32 (nativeData) ; var bytes = new byte[length] ; Marshal.Copy (new IntPtr ((long)nativeData + 4), bytes, 0, length) ; 

Si los datos que se guardan no son una cadena, no es necesario almacenarlos en una cadena. Por lo general, no mariscal a una cadena a menos que el tipo de datos original fuera un char* . De lo contrario, un byte[] debería hacer.

Tratar:

 [MarshalAs(UnmanagedType.ByValArray, SizeConst=[whatever your size is]] byte[] Data; 

Si necesita convertir esto a una cadena más tarde, use:

 System.Text.Encoding.UTF8.GetString(your byte array here). 

Obviamente, necesita variar la encoding a lo que necesita, aunque UTF-8 generalmente es suficiente.

Veo el problema ahora, tienes que calcular una matriz de longitud VARIABLE. MarshalAs no permite esto y la matriz deberá enviarse por referencia.

Si la longitud de la matriz es variable, su byte[] necesita ser un IntPtr, por lo que debería usar,

 IntPtr Data; 

En lugar de

 [MarshalAs(UnmanagedType.ByValArray, SizeConst=[whatever your size is]] byte[] Data; 

Luego puede usar la clase Marshal para acceder a los datos subyacentes.

Algo como:

 uint length = yourABSObject.Length; byte[] buffer = new byte[length]; Marshal.Copy(buffer, 0, yourABSObject.Data, length); 

Puede que necesite limpiar su memoria cuando haya terminado para evitar una fuga, aunque sospecho que el GC la limpiará cuando su OBSOb esté fuera del scope. De todos modos, aquí está el código de limpieza:

 Marshal.FreeHGlobal(yourABSObject.Data); 

Está intentando byte[ABS_VARLEN] algo que es un byte[ABS_VARLEN] como si fuera una string de longitud 1. Necesitará averiguar cuál es la constante ABS_VARLEN y ordenar la matriz como:

 [MarshalAs(UnmanagedType.LPArray, SizeConst = 1024)] public byte[] Data; 

(El 1024 hay un marcador de posición; complete el valor real de ASB_VARLEN que corresponda).

En mi opinión, es más simple y más eficiente anclar la matriz y tomar su dirección.

Suponiendo que necesita pasar abs_data a myNativeFunction(abs_data*) :

 public struct abs_data { public uint Length; public IntPtr Data; } [DllImport("myDll.dll")] static extern void myNativeFunction(ref abs_data data); void CallNativeFunc(byte[] data) { GCHandle pin = GCHandle.Alloc(data, GCHandleType.Pinned); abs_data tmp; tmp.Length = data.Length; tmp.Data = pin.AddrOfPinnedObject(); myNativeFunction(ref tmp); pin.Free(); }