2009-09-28 2 views
11

Я размещаю некоторую неуправляемую память в своем приложении через Marshal.AllocHGlobal. Затем я копирую набор байтов в это место и преобразовывая полученный сегмент памяти в struct, прежде чем снова освободить память через Marshal.FreeHGlobal.Как обнулить память, выделенную Marshal.AllocHGlobal?

Вот метод:

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

Это работает по большей части, однако, если у меня есть меньше байтов, чем размер struct требует, то «случайные» присвоены значения последних полей (я используя LayoutKind.Sequential на целевой структуре). Я бы хотел как можно лучше обнулить эти висячие поля.

Для контекста этот код десеризует высокочастотные многоадресные сообщения, отправленные с C++ в Linux.

Вот неудача теста:

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

Выполнение этого теста повторно вызывает второй Assert к сбою с другим значением каждый раз.


EDIT

В конце концов, я использовал leppie's suggestion идти unsafe и используя stackalloc. Это распределяло массив байтов, который был обнулен по мере необходимости, и улучшал пропускную способность от 50% до 100%, в зависимости от размера сообщения (большие сообщения видели большую выгоду).

Последний метод кончались напоминающего:

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

К сожалению, это все еще требует вызова Marshal.PtrToStructure, чтобы преобразовать байты в целевой тип.

ответ

2

Почему бы не просто проверить, есть ли start + length в пределах typesize?

BTW: Я бы просто пошел unsafe и использовал цикл for, чтобы обнулить дополнительную память.

Это также даст вам преимущество использования stackalloc, который намного безопаснее и быстрее, чем AllocGlobal.

+0

@leppie - спасибо за полезную информацию. Я также проверю 'stackalloc'. Я должен удовлетворять различным размерам сообщений, так как две команды могут иногда избегать синхронизированных выпусков, если мы добавим поля в конце, которые игнорирует другой конец. Точно так же, если вам не нужны значения, вы можете ожидать их и получить нули вместо этого, что я и делаю здесь. –

+0

@leppie, я склоняюсь к этому подходу. Не могли бы вы подробнее рассказать о том, _why_, используя 'stackalloc', безопаснее и быстрее? Как только у меня есть 'byte *', какой будет лучший способ скопировать его? –

+0

Я собрал версию, которая работает с 'stackalloc', чтобы заполнить массив в стеке. Я не думаю, что можно обойти вызов «Marshal.PtrToStructure», правда? –

2

Я никогда раньше не делал этого в C#, но я нашел Marshal.WriteByte (IntPtr, Int32, Byte) в MSDN. Попробуйте это.

2

Да, как Jon Seigel сказал, вы можете обнулить его с помощью Marshal.WriteByte

В следующем примере я обнулить буфер перед копированием структуры.

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

Каждый вызов Marshal.WriteByte приведет к переходу между управляемым и нативным кодом и обратно, что имеет определенные накладные расходы. Выполнение этого в цикле может стать неэффективным. чтобы придерживаться класса маршала, я бы попробовал это вместо этого: Marshal.Copy (новый байт [typeSize], 0, targetBytes, typeSize) –

+0

Другая альтернатива, о которой я думал, это P/Invoke the LocalAlloc и передать в LPTR –

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

Доски говорят, что это макрос. –

+1

Dumpbin.exe на kernel32.dll говорит, что это не просто макрос. –

+0

@MattiasS - Мне нужно обнулить на 'dst + N'. 'IntPtr' не поддерживает арифметику, так как я могу устранить это смещение? –

6

Это будет прекрасно работать на 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

Я думаю, что лучший способ обнулить буфер это, если вы не хотите, или не можете пойти другой способ:

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

Я думаю, вы найдете его, чтобы быть в несколько раз быстрее нас с 64-битными борами вместо 8 бит-бит (которые вам все еще нужны для последних нескольких байтов).