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.
@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ąć. –
@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? –
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? –