2009-09-28 23 views
11

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.

Respuesta

2

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

BTW: Me gustaría ir unsafe aquí y usar un bucle for para poner a cero la memoria adicional.

Eso también le dará la ventaja de usar stackalloc que es mucho más seguro y más rápido que AllocGlobal.

+0

@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í. –

+0

@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? –

+0

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í? –

2

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

2

Sí como Jon Seigel Dicho esto, usted puede poner a cero a cabo utilizando Marshal.WriteByte

En el siguiente ejemplo, ajustar a cero la memoria intermedia 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); 
+7

Cada llamada a Marshal.WriteByte provocará una transición entre código administrado y nativo y viceversa, que tiene una cierta sobrecarga. Hacer eso en un ciclo puede volverse ineficiente. Si quieres para seguir la clase Marshal, probaría esto en su lugar: Marshal.Copy (new byte [typeSize], 0, targetBytes, typeSize) –

+0

La otra alternativa en la que estaba pensando era P/Invocar la función LocalAlloc y pasar en el LPTR –

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

Las doscs dicen que es una macro. –

+1

Dumpbin.exe en kernel32.dll dice que no es solo una macro. –

+0

@MattiasS - Necesito poner a cero en 'dst + N'. 'IntPtr' no es compatible con la aritmética, así que ¿cómo puedo abordar este desplazamiento? –

6

Esto funciona 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); 
    } 
} 
0

Creo que la mejor manera de poner a cero un buffer es esto, si usted no quiere o no puede ir a la otra manera:

for(int i=0; i<buffSize; i++) 
{ 
    Marshal.WriteByte(buffer, i, 0x00); 
} 
2
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 le resultará ser varias veces más rápido que nos en wrights de 64 bits en lugar de wrights de 8 bits (que aún necesita para los últimos bytes).

Cuestiones relacionadas