Маршал.PtrToStructure (и обратно) и общее решение для замены endianness
у меня есть система, где удаленный агент отправляет сериализованные структуры (из встроенной системы C) для чтения и хранения через IP/UDP. В некоторых случаях мне нужно отправить обратно те же типы структур. Я думал, что у меня хорошая установка с маршалом.PtrToStructure (получать) и Маршал.StructureToPtr (отправить). Однако небольшой gotcha заключается в том, что сетевые большие целые числа должны быть преобразованы в мой формат x86 little endian для локального использования. Когда я отправляю их снова, большой эндиан-это путь к идти.
вот вопрос:
private static T BytesToStruct<T>(ref byte[] rawData) where T: struct
{
T result = default(T);
GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned);
try
{
IntPtr rawDataPtr = handle.AddrOfPinnedObject();
result = (T)Marshal.PtrToStructure(rawDataPtr, typeof(T));
}
finally
{
handle.Free();
}
return result;
}
private static byte[] StructToBytes<T>(T data) where T: struct
{
byte[] rawData = new byte[Marshal.SizeOf(data)];
GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned);
try
{
IntPtr rawDataPtr = handle.AddrOfPinnedObject();
Marshal.StructureToPtr(data, rawDataPtr, false);
}
finally
{
handle.Free();
}
return rawData;
}
и быстрый пример структуры, которая может быть использована следующим образом:
byte[] data = this.sock.Receive(ref this.ipep);
Request request = BytesToStruct<Request>(ref data);
где структура, о которой идет речь, выглядит так:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
private struct Request
{
public byte type;
public short sequence;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
public byte[] address;
}
каким (общим) способом я могу поменять endianness при сортировке структур? Моя потребность такова, что локально сохраненный запрос.последовательность в этом примере должен быть обратным для отображения пользователю. Я не хочу, чтобы мне пришлось менять endianness в структурно-специфический способ, поскольку это общая проблема.
моей первой мыслью было использовать отражение, но я не очень хорошо знаком с этой особенностью. Кроме того, я надеялся, что там будет лучшее решение, на которое кто-то может указать мне. Заранее спасибо:)
4 ответов
отражение действительно кажется единственным реальным способом достичь того, что вам нужно.
Я собрал некоторый код ниже. Он создает атрибут с именем EndianAttribute
это может быть применено на уровне поля в структуре. Я включил определение этого атрибута, и это связанное перечисление, а также изменения в вашем коде, необходимые для его использования.
в качестве примечания, вам не нужно было определять rawData
как
для тех из нас, кто без Linq, замена RespectEndianness()
:
private static void RespectEndianness(Type type, byte[] data) {
foreach (FieldInfo f in type.GetFields()) {
if (f.IsDefined(typeof(EndianAttribute), false)) {
EndianAttribute att = (EndianAttribute)f.GetCustomAttributes(typeof(EndianAttribute), false)[0];
int offset = Marshal.OffsetOf(type, f.Name).ToInt32();
if ((att.Endianness == Endianness.BigEndian && BitConverter.IsLittleEndian) ||
(att.Endianness == Endianness.LittleEndian && !BitConverter.IsLittleEndian)) {
Array.Reverse(data, offset, Marshal.SizeOf(f.FieldType));
}
}
}
}
вот мой вариант-он обрабатывает вложенные структуры и массивы, с предположением, что массивы имеют фиксированный размер, например, помечены [MarshalAs(UnmanagedType.Атрибут ByValArray, SizeConst = Н)].
public static class Serializer
{
public static byte[] GetBytes<T>(T structure, bool respectEndianness = true) where T : struct
{
var size = Marshal.SizeOf(structure); //or Marshal.SizeOf<T>(); in .net 4.5.1
var bytes = new byte[size];
var ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(structure, ptr, true);
Marshal.Copy(ptr, bytes, 0, size);
Marshal.FreeHGlobal(ptr);
if (respectEndianness) RespectEndianness(typeof(T), bytes);
return bytes;
}
public static T FromBytes<T>(byte[] bytes, bool respectEndianness = true) where T : struct
{
var structure = new T();
if (respectEndianness) RespectEndianness(typeof(T), bytes);
int size = Marshal.SizeOf(structure); //or Marshal.SizeOf<T>(); in .net 4.5.1
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.Copy(bytes, 0, ptr, size);
structure = (T)Marshal.PtrToStructure(ptr, structure.GetType());
Marshal.FreeHGlobal(ptr);
return structure;
}
private static void RespectEndianness(Type type, byte[] data, int offSet = 0)
{
var fields = type.GetFields()
.Select(f => new
{
Field = f,
Offset = Marshal.OffsetOf(type, f.Name).ToInt32(),
}).ToList();
foreach (var field in fields)
{
if (field.Field.FieldType.IsArray)
{
//handle arrays, assuming fixed length
var attr = field.Field.GetCustomAttributes(typeof(MarshalAsAttribute), false).FirstOrDefault();
var marshalAsAttribute = attr as MarshalAsAttribute;
if (marshalAsAttribute == null || marshalAsAttribute.SizeConst == 0)
throw new NotSupportedException(
"Array fields must be decorated with a MarshalAsAttribute with SizeConst specified.");
var arrayLength = marshalAsAttribute.SizeConst;
var elementType = field.Field.FieldType.GetElementType();
var elementSize = Marshal.SizeOf(elementType);
var arrayOffset = field.Offset + offSet;
for (int i = arrayOffset; i < arrayOffset + elementSize * arrayLength; i += elementSize) {
RespectEndianness(elementType, data, i);
}
}
else if (!field.Field.FieldType.IsPrimitive) //or !field.Field.FiledType.GetFields().Length == 0
{
//handle nested structs
RespectEndianness(field.Field.FieldType, data, field.Offset);
}
else
{
//handle primitive types
Array.Reverse(data, offSet + field.Offset, Marshal.SizeOf(field.Field.FieldType));
}
}
}
}
этот вопрос был потрясающим, и мне очень помогли! Мне нужно было расширить endian changer, хотя, похоже, он не обрабатывает массивы или структуры внутри структур.
public struct mytest
{
public int myint;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public int[] ptime;
}
public static void SwapIt(Type type, byte[] recvbyte, int offset)
{
foreach (System.Reflection.FieldInfo fi in type.GetFields())
{
int index = Marshal.OffsetOf(type, fi.Name).ToInt32() + offset;
if (fi.FieldType == typeof(int))
{
Array.Reverse(recvbyte, index, sizeof(int));
}
else if (fi.FieldType == typeof(float))
{
Array.Reverse(recvbyte, index, sizeof(float));
}
else if (fi.FieldType == typeof(double))
{
Array.Reverse(recvbyte, index, sizeof(double));
}
else
{
// Maybe we have an array
if (fi.FieldType.IsArray)
{
// Check for MarshalAs attribute to get array size
object[] ca = fi.GetCustomAttributes(false);
if (ca.Count() > 0 && ca[0] is MarshalAsAttribute)
{
int size = ((MarshalAsAttribute)ca[0]).SizeConst;
// Need to use GetElementType to see that int[] is made of ints
if (fi.FieldType.GetElementType() == typeof(int))
{
for (int i = 0; i < size; i++)
{
Array.Reverse(recvbyte, index + (i * sizeof(int)), sizeof(int));
}
}
else if (fi.FieldType.GetElementType() == typeof(float))
{
for (int i = 0; i < size; i++)
{
Array.Reverse(recvbyte, index + (i * sizeof(float)), sizeof(float));
}
}
else if (fi.FieldType.GetElementType() == typeof(double))
{
for (int i = 0; i < size; i++)
{
Array.Reverse(recvbyte, index + (i * sizeof(double)), sizeof(double));
}
}
else
{
// An array of something else?
Type t = fi.FieldType.GetElementType();
int s = Marshal.SizeOf(t);
for (int i = 0; i < size; i++)
{
SwapIt(t, recvbyte, index + (i * s));
}
}
}
}
else
{
SwapIt(fi.FieldType, recvbyte, index);
}
}
}
}
обратите внимание, что этот код был протестирован только на структурах из int, float, double. Вероятно, испортит, если у вас есть строка там!