Estoy asignando un poco de memoria no administrada en mi aplicación a través de Marshal.AllocHGlobal
. Estoy copiando un conjunto de bytes a esta ubicación y convirtiendo el segmento resultante de la memoria a struct
antes de liberar la memoria nuevamente a través del Marshal.FreeHGlobal
.Cómo poner a cero la memoria asignada por Marshal.AllocHGlobal?
Aquí está el método:
public static T Deserialize<T>(byte[] messageBytes, int start, int length)
where T : struct
{
if (start + length > messageBytes.Length)
throw new ArgumentOutOfRangeException();
int typeSize = Marshal.SizeOf(typeof(T));
int bytesToCopy = Math.Min(typeSize, length);
IntPtr targetBytes = Marshal.AllocHGlobal(typeSize);
Marshal.Copy(messageBytes, start, targetBytes, bytesToCopy);
if (length < typeSize)
{
// Zero out additional bytes at the end of the struct
}
T item = (T)Marshal.PtrToStructure(targetBytes, typeof(T));
Marshal.FreeHGlobal(targetBytes);
return item;
}
Esto funciona en su mayor parte, sin embargo si tengo menos bytes que el tamaño de la struct
requiere, entonces los valores 'al azar' se asignan a los últimos campos (soy usando LayoutKind.Sequential
en la estructura de destino). Me gustaría poner a cero estos campos colgantes de la manera más eficiente posible.
Por contexto, este código deserializa mensajes de multidifusión de alta frecuencia enviados desde C++ en Linux.
Aquí es un caso de prueba en su defecto:
// Give only one byte, which is too few for the struct
var s3 = MessageSerializer.Deserialize<S3>(new[] { (byte)0x21 });
Assert.AreEqual(0x21, s3.Byte);
Assert.AreEqual(0x0000, s3.Int); // hanging field should be zero, but isn't
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
private struct S3
{
public byte Byte;
public int Int;
}
Al ejecutar esta prueba en repetidas ocasiones hace que la segunda aserción a fallar con un valor diferente cada vez.
EDITAR
Al final, solía leppie's suggestion de ir unsafe
y usando stackalloc
. Esto asignó una matriz de bytes que se puso a cero según sea necesario, y un rendimiento mejorado de entre 50% y 100%, dependiendo del tamaño del mensaje (los mensajes más grandes tuvieron un mayor beneficio).
El método final terminó pareciéndose:
public static T Deserialize<T>(byte[] messageBytes, int startIndex, int length)
where T : struct
{
if (length <= 0)
throw new ArgumentOutOfRangeException("length", length, "Must be greater than zero.");
if (startIndex < 0)
throw new ArgumentOutOfRangeException("startIndex", startIndex, "Must be greater than or equal to zero.");
if (startIndex + length > messageBytes.Length)
throw new ArgumentOutOfRangeException("length", length, "startIndex + length must be <= messageBytes.Length");
int typeSize = Marshal.SizeOf(typeof(T));
unsafe
{
byte* basePtr = stackalloc byte[typeSize];
byte* b = basePtr;
int end = startIndex + Math.Min(length, typeSize);
for (int srcPos = startIndex; srcPos < end; srcPos++)
*b++ = messageBytes[srcPos];
return (T)Marshal.PtrToStructure(new IntPtr(basePtr), typeof(T));
}
}
Desafortunadamente esto todavía requiere una llamada a Marshal.PtrToStructure
para convertir los bytes en el tipo de destino.
@leppie - gracias por la información útil. Verificaré 'stackalloc' también. Tengo que atender los diferentes tamaños de mensajes ya que los dos equipos pueden ocasionalmente evitar los lanzamientos sincronizados si agregamos campos al final que el otro extremo ignora. Del mismo modo, si no requiere valores, puede esperarlos y obtener ceros, que es el caso que estoy tratando de lograr aquí. –
@leppie, me inclino por este enfoque. ¿Podrías entrar en más detalles sobre _why_ usando 'stackalloc' es más seguro y más rápido? Una vez que tenga el 'byte *', ¿cuál sería la mejor manera de copiarlo? –
He creado una versión que funciona con 'stackalloc' para poblar una matriz en la pila. No creo que sea posible solucionar la llamada a 'Marshal.PtrToStructure', ¿o sí? –