2009-09-28 14 views
11

Przydzielam niektóre niezarządzane pamięci w mojej aplikacji przez Marshal.AllocHGlobal. Następnie kopiuję zestaw bajtów do tej lokalizacji i konwertuję wynikowy segment pamięci na struct przed zwolnieniem pamięci ponownie poprzez Marshal.FreeHGlobal.Jak wyzerować pamięć przydzieloną przez Marshal.AllocHGlobal?

Oto sposób:

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

Działa to w przeważającej części, jednak jeśli mam mniej bajtów niż wielkość struct wymaga, wtedy wartości „losowe” są przypisywane do ostatnich pól (jestem za pomocą LayoutKind.Sequential na docelowej strukturze). Chciałbym jak najlepiej wyzerować te wiszące pola.

Dla kontekstu ten kod jest deserializacją komunikatów multiemisji o wysokiej częstotliwości wysyłanych z C++ w systemie Linux.

Oto braku przypadek testowy:

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

Uruchomienie tego testu wielokrotnie powoduje awarię drugiego assert z inną wartość za każdym razem.


EDIT

W końcu użyłem leppie's suggestion iść unsafe i korzystania stackalloc. W ten sposób przydzielono tablicę bajtów, która w razie potrzeby została wyzerowana, a także zwiększono przepustowość z 50% do 100%, w zależności od rozmiaru komunikatu (większe wiadomości widziały większą korzyść).

Ostateczny sposób zakończył się przypominające:

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

Niestety ta nadal wymaga połączenia do Marshal.PtrToStructure przekonwertować bajty na typ docelowy.

Odpowiedz

2

Dlaczego po prostu sprawdzić, czy start + length jest w zakresie typesize?

BTW: Chciałbym po prostu przejść tutaj unsafe i użyć pętli for, aby wyzerować dodatkową pamięć.

To również daje korzyści z używania stackalloc, co jest znacznie bezpieczniejsze i szybsze niż AllocGlobal.

+0

@leppie - dzięki za przydatne informacje. Sprawdzę też 'stackalloc'. Muszę uwzględnić różne rozmiary komunikatów, ponieważ dwa zespoły mogą czasami uniknąć wydania zsynchronizowanego, jeśli dodamy pola na końcu, którego drugi koniec ignoruje. Podobnie, jeśli nie potrzebujesz wartości, możesz oczekiwać ich i uzyskać zamiast nich wartości zerowe, co staram się tutaj osiągnąć. –

+0

@leppie, jestem skłonny do tego podejścia. Czy mógłbyś bardziej szczegółowo opisać, dlaczego używanie 'stackalloc' jest bezpieczniejsze i szybsze? Kiedy mam 'byte *', jaki byłby najlepszy sposób na skopiowanie go? –

+0

Połączyłem wersję, która działa z 'stackalloc', aby zapełnić tablicę na stosie. Nie sądzę, że możliwe jest obejście połączenia z 'Marshal.PtrToStructure', prawda? –

2

Nigdy wcześniej nie robiłem tego w C#, ale znalazłem Marshal.WriteByte (IntPtr, Int32, Byte) w MSDN. Wypróbuj to.

2

Tak jak Jon Seigel powiedział, można wyzerować go używając Marshal.WriteByte

W poniższym przykładzie, wyzerować bufor przed skopiowaniem 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); 
//zero out buffer 
for(int i=0; i < typeSize; i++) 
{ 
    Marshal.WriteByte(targetBytes, i, 0); 
} 
Marshal.Copy(messageBytes, start, targetBytes, bytesToCopy); 
+7

Każde wywołanie Marshal.WriteByte spowoduje przejście między kodem zarządzanym a natywnym iz powrotem, co wiąże się z pewnym obciążeniem, a wykonanie tego w pętli może stać się nieefektywne. trzymać się klasy Marshala, spróbuję tego zamiast: Marshal.Copy (nowy bajt [typeSize], 0, targetBytes, typeSize) –

+0

Inną alternatywą, o której myślałem było P/Wywołanie funkcji LocalAlloc i podanie w LPTR flag: –

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

Dyski mówią, że to makro, –

+1

Dumpbin.exe na kernel32.dll mówi, że to nie jest tylko makro, –

+0

@MattiasS - muszę wyzerować w dniu 'dst + N'. 'IntPtr' nie obsługuje arytmetyki, więc jak mogę adresować to przesunięcie? –

6

to będzie działać dobrze na 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

Myślę, że najlepszym sposobem, aby wyzerować bufor to jest, jeśli nie chcą lub nie mogą przejść inny sposób:

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

myślę, że będzie go znaleźć się kilka razy szybciej nas zamiast writerów z 8-bitowymi wrightami (które wciąż potrzebujesz dla ostatnich kilku bajtów).

Powiązane problemy