Я размещаю некоторую неуправляемую память в своем приложении через 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
, чтобы преобразовать байты в целевой тип.
@leppie - спасибо за полезную информацию. Я также проверю 'stackalloc'. Я должен удовлетворять различным размерам сообщений, так как две команды могут иногда избегать синхронизированных выпусков, если мы добавим поля в конце, которые игнорирует другой конец. Точно так же, если вам не нужны значения, вы можете ожидать их и получить нули вместо этого, что я и делаю здесь. –
@leppie, я склоняюсь к этому подходу. Не могли бы вы подробнее рассказать о том, _why_, используя 'stackalloc', безопаснее и быстрее? Как только у меня есть 'byte *', какой будет лучший способ скопировать его? –
Я собрал версию, которая работает с 'stackalloc', чтобы заполнить массив в стеке. Я не думаю, что можно обойти вызов «Marshal.PtrToStructure», правда? –