recorrer - ¿Cómo calculo una estructura que contiene una matriz de tamaño variable a C#?
recorrer matriz c# (5)
En mi opinión, es más simple y más eficiente fijar 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();
}
¿Cómo calculo este tipo de C ++?
La estructura ABS_DATA se utiliza 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 viene dada por el miembro 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 está siempre 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;
}
No es posible calcular estructuras que contengan matrices de longitud variable (pero es posible ordenar matrices de longitud variable como parámetros de función). Tendrás que leer tus 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 tiene que almacenarlos en una cadena. Por lo general, no calculo a una cadena a menos que el tipo de datos original sea un char*
. De lo contrario, un byte[]
debería hacer.
Tratar:
[MarshalAs(UnmanagedType.ByValArray, SizeConst=[whatever your size is]]
byte[] Data;
Si necesitas convertir esto a una cadena más tarde, usa:
System.Text.Encoding.UTF8.GetString(your byte array here).
Obviamente, necesita variar la codificación a lo que necesita, aunque UTF-8 generalmente es suficiente.
Veo el problema ahora, tienes que reunir 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[]
debe ser un IntPtr, por lo que usaría
IntPtr Data;
En lugar de
[MarshalAs(UnmanagedType.ByValArray, SizeConst=[whatever your size is]]
byte[] Data;
A continuación, puede utilizar 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);
Es posible que deba limpiar su memoria cuando haya terminado para evitar una fuga, aunque sospecho que el GC la limpiará cuando su objeto ABSO se salga del alcance. De todos modos, aquí está el código de limpieza:
Marshal.FreeHGlobal(yourABSObject.Data);
Usted está tratando de ordenar algo que es un byte[ABS_VARLEN]
como si fuera una string
de longitud 1. Deberá averiguar cuál es la constante ABS_VARLEN y ordenar la matriz como:
[MarshalAs(UnmanagedType.LPArray, SizeConst = 1024)]
public byte[] Data;
(El 1024 es un marcador de posición; complete el valor real de ASB_VARLEN).
Pregunta antigua, pero recientemente tuve que hacerlo yo mismo y todas las respuestas existentes son deficientes, así que ...
La mejor solución para calcular 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 entre datos administrados y no administrados. Desafortunadamente, el cálculo de personalizaciones está mal documentado y tiene algunas limitaciones extrañas. Los cubriré rápidamente, luego repasaré la solución.
De manera molesta, no puede usar el cálculo de referencias personalizado en un elemento de matriz de una estructura o clase. No hay ninguna razón lógica o documentada para esta limitación, y el compilador no se quejará, pero obtendrá una excepción en el tiempo de ejecución. Además, hay una función que los calificadores personalizados deben implementar, int GetNativeDataSize()
, que obviamente es imposible de implementar con precisión (no le pasa una instancia del objeto para pedir su tamaño, por lo que solo puede desactivar el tipo, que Por supuesto, ¡es de tamaño variable!) Afortunadamente, esta función no importa. Nunca lo he visto llamar, y el contador de referencias personalizado funciona bien incluso si devuelve un valor falso (un ejemplo de MSDN hace que devuelva -1).
En primer lugar, esto es lo que creo que podría parecer su prototipo nativo (estoy usando P / Invoke 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ía haber utilizado un contador de referencias personalizado (que realmente debería haber funcionado). Voy a llegar al mariscal 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<byte>))]
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 el tiempo de ejecución, aparentemente no puede ordenar matrices dentro de estructuras de datos como cualquier otra cosa, excepto SafeArray
o ByValArray
. Los SafeArrays se cuentan, pero no se parecen en nada al formato (extremadamente común) que está buscando aquí. Así que eso no funcionará. ByValArray, por supuesto, requiere que la longitud sea conocida en el momento de la compilación, por lo que tampoco funciona (como se encontró con). Extrañamente, sin embargo, puede usar cálculo de referencias personalizado en los parámetros de la matriz. Esto es molesto porque tiene que poner el MarshalAsAttribute
en cada parámetro que usa este tipo, en lugar de solo ponerlo en un campo y hacer que se aplique en cualquier lugar que use el tipo que contiene 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<abs_data>))]
// 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 quieras hacer algo especial con él (constructores, funciones estáticas, propiedades, herencia, lo que sea). Si los elementos de su matriz consistieran 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 renombrado, ni siquiera es "envolver" el byte; en lo que respecta al código nativo, es más como un typedef, así que puedes 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>))]
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, todavía necesitamos ese contador de personal personalizado. Debería leer " Implementación de la interfaz de ICustomMarshaler " pero cubriré esto aquí, con comentarios en línea. Tenga en cuenta que utilizo algunas convenciones abreviadas (como Marshal.SizeOf<T>()
) 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<T> : ICustomMarshaler
{
// All custom marshalers require a static factory method with this signature.
public static ICustomMarshaler GetInstance (String cookie)
{
return new ArrayMarshaler<T>();
}
// 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<T>();
// Populate the array
for (int i = 0; i < length; i++)
{
array[i] = Marshal.PtrToStructure<T>(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<T>();
// 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<T>(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<T>();
}
}
¡Menos mal, eso fue largo! Bueno, ahí lo tienes. Espero que la gente vea esto, porque hay muchas malas respuestas y malentendidos por ahí ...