.net memory unmanaged-memory

.net - Cómo poner a cero la memoria asignada por Marshal.AllocHGlobal?



memory unmanaged-memory (7)

Estoy asignando memoria no administrada en mi aplicación a través de Marshal.AllocHGlobal . Luego copio un conjunto de bytes en esta ubicación y convierto el segmento de memoria resultante en una struct antes de liberar la memoria nuevamente a través de Marshal.FreeHGlobal .

Este es 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 , entonces los valores ''aleatorios'' se asignan a los últimos campos (estoy 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 está deserializando mensajes de multidifusión de alta frecuencia enviados desde C ++ en Linux.

Aquí hay un caso de prueba fallido:

// 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; }

Ejecutar esta prueba repetidamente hace que la segunda afirmación falle con un valor diferente cada vez.

EDITAR

Al final, utilicé la sugerencia de stackalloc unsafe ser unsafe y usar 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 a:

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)); } }

Lamentablemente, esto todavía requiere una llamada a Marshal.PtrToStructure para convertir los bytes en el tipo de destino.


¿Por qué no solo comprobar si start + length está dentro de typesize ?

Por cierto: me gustaría ir unsafe aquí y utilizar un bucle for para poner a cero la memoria adicional.

Eso también te dará el beneficio de usar stackalloc que es mucho más seguro y más rápido que AllocGlobal .


Creo que la mejor manera de poner a cero un búfer es esto, si no quieres, o no puedes ir por el otro camino:

for(int i=0; i<buffSize; i++) { Marshal.WriteByte(buffer, i, 0x00); }


Esto funcionará bien en Windows:

namespace KernelPInvoke { /// <summary> /// Implements some of the C functions declared in string.h /// </summary> public static class MemoryWrapper { [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)] static extern void CopyMemory(IntPtr destination, IntPtr source, uint length); [DllImport("kernel32.dll", EntryPoint = "MoveMemory", SetLastError = false)] static extern void MoveMemory(IntPtr destination, IntPtr source, uint length); [DllImport("kernel32.dll", EntryPoint = "RtlFillMemory", SetLastError = false)] static extern void FillMemory(IntPtr destination, uint length, byte fill); } var ptr = Marshal.AllocHGlobal(size); try { MemoryWrapper.FillMemory(ptr, size, 0); // further work... } finally { Marshal.FreeHGlobal(ptr); } }


Nunca antes había hecho esto en C #, pero encontré Marshal.WriteByte (IntPtr, Int32, Byte) en MSDN. Pruébalo.


Sí, como dijo Jon Seigel , puedes ponerlo en cero usando Marshal.WriteByte

En el siguiente ejemplo, pongo a cero el búfer antes de copiar la estructura.

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); //zero out buffer for(int i=0; i < typeSize; i++) { Marshal.WriteByte(targetBytes, i, 0); } Marshal.Copy(messageBytes, start, targetBytes, bytesToCopy);


for(int i=0; i < buffSize / 8; i += 8 ) { Marshal.WriteInt64(buffer, i, 0x00); } for(int i= buffSize % 8 ; i < -1 ; i-- ) { Marshal.WriteByte (buffer, buffSize - i, 0x00); }

Creo que lo encontrará varias veces más rápido usando wrights de 64 bits en lugar de wrights de 8 bits (que aún necesita para los últimos bytes).


[DllImport("kernel32.dll")] static extern void RtlZeroMemory(IntPtr dst, int length); ... RtlZeroMemory(targetBytes, typeSize);