问题
Based on suggestions from svick, I believe I can substancially simplify my post and question. Below, is some complete code that demonstrates my problem and question, namely marshalling bytes to structures does not work the way I'd expect... In the case of an object with two arrays seperated by another primitive, my object is not being marshalled to bytes the way I'd expect. Although I specify "Sequential", the two byte[] arrays are put in the byte array first, with the uint following. What is going on? In "real life" I am dealing with a binary file from someone else, where the data really is in the order byte[5] firstArray, uint firmwareVersion, byte[9] secondArray.
As requested, I have put a complete code example with comments.
namespace MarshallingExample
{
class Program
{
static void Main(string[] args)
{
// first show that our Marshalling to/from objects to bytes is working ...
Simple simpleObject = new Simple();
simpleObject.HeaderInfo = new byte[5] { 1, 2, 3, 4, 5 };
simpleObject.FirmwareRevision = 1234;
byte[] simpleObjectBytes = Tools.ConvertObjectToBytes(simpleObject);
Simple simpleObject2 = Tools.ConvertBytesToObject(simpleObjectBytes, 0, typeof(Simple)) as Simple;
Complex complexObject = new Complex();
complexObject.HeaderInfo = new byte[5] { 1, 2, 3, 4, 5 };
complexObject.FirmwareRevision = 1234;
complexObject.SoftwarePartNumber = new byte[9] { 11, 12, 13, 14, 15, 16, 17, 18, 19 };
byte[] complexObjectBytes = Tools.ConvertObjectToBytes(complexObject); // look at complexObjectBytes!!!
// Notice that the two arrays come first in complexObjectBytes. Not a problem here.
Complex complexObject2 = Tools.ConvertBytesToObject(complexObjectBytes, 0, typeof(Complex)) as Complex;
// Set up some data in MemoryStream(would really be in files) as it's actually given to us....
MemoryStream memStreamSimple;
memStreamSimple = new MemoryStream(9);
memStreamSimple.Write(simpleObject.HeaderInfo, 0, 5);
memStreamSimple.Write(BitConverter.GetBytes(simpleObject.FirmwareRevision), 0, 4);
MemoryStream memStreamComplex;
memStreamComplex = new MemoryStream(18);
memStreamComplex.Write(complexObject.HeaderInfo, 0, 5);
memStreamComplex.Write(BitConverter.GetBytes(complexObject.FirmwareRevision), 0, 4);
memStreamComplex.Write(complexObject.SoftwarePartNumber, 0, 9);
// now read and try to put in our structures....
memStreamSimple.Seek(0, SeekOrigin.Begin);
simpleObjectBytes = new byte[9];
int count = memStreamSimple.Read(simpleObjectBytes, 0, 9);
simpleObject2 = Tools.ConvertBytesToObject(simpleObjectBytes, 0, typeof(Simple)) as Simple;
memStreamComplex.Seek(0, SeekOrigin.Begin);
complexObjectBytes = new byte[18];
count = memStreamComplex.Read(complexObjectBytes, 0, 18);
// Note that following object is all messed up, probably because it was expecting the two arrays first.
complexObject2 = Tools.ConvertBytesToObject(complexObjectBytes, 0, typeof(Complex)) as Complex;
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class Simple
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
private byte[] _headerInfo = new byte[5];
public byte[] HeaderInfo
{
get
{
return _headerInfo;
}
set
{
_headerInfo = value;
}
}
public uint FirmwareRevision
{
get;
set;
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class Complex
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
private byte[] _headerInfo = new byte[5];
public byte[] HeaderInfo
{
get
{
return _headerInfo;
}
set
{
_headerInfo = value;
}
}
public uint FirmwareRevision
{
get;
set;
}
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 9)]
private byte[] _softwarePartNumber = new byte[9];
public byte[] SoftwarePartNumber
{
get
{
return _softwarePartNumber;
}
set
{
_softwarePartNumber = value;
}
}
}
public static class Tools
{
/// <summary>
/// Convert an array of bytes starting at the offset provided into the specified type (struct or class).
/// </summary>
/// <param name="bytes"></param>
/// <param name="startOffset"></param>
/// <param name="type"></param>
/// <returns>Newly created object of the specified type created from bytes given. NULL on any error.</returns>
public static object ConvertBytesToObject(byte[] bytes, int startOffset, Type type)
{
object obj = null;
int size = Marshal.SizeOf(type);
if (size > 0)
{
if (size <= bytes.Length - startOffset)
{
IntPtr ptr = IntPtr.Zero;
try
{
ptr = Marshal.AllocHGlobal(size);
Marshal.Copy(bytes, startOffset, ptr, size);
obj = Marshal.PtrToStructure(ptr, type);
}
finally
{
if (ptr != IntPtr.Zero)
{
Marshal.FreeHGlobal(ptr);
}
}
}
else
{
throw new Exception("ConvertBytesToObject: Requested offset + size of object exceeds length of bytes buffer to read.");
}
}
else
{
throw new Exception("ConvertBytesToObject: Marshal.SizeOf(T) returned a size of 0.");
}
return obj;
}
/// <summary>
/// Convert an object (struct or class) to an array of bytes.
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static byte[] ConvertObjectToBytes(object obj)
{
byte[] bytes = null;
if (obj != null)
{
IntPtr ptr = IntPtr.Zero;
Type type = obj.GetType();
int size = Marshal.SizeOf(type);
//int size = Marshal.SizeOf(obj.GetType());
if (size > 0)
{
try
{
ptr = Marshal.AllocHGlobal(size);
if (ptr != IntPtr.Zero)
{
Marshal.StructureToPtr(obj, ptr, false);
bytes = new byte[size];
Marshal.Copy(ptr, bytes, 0, size);
}
}
finally
{
if (ptr != IntPtr.Zero)
{
Marshal.FreeHGlobal(ptr);
}
}
}
else
{
throw new Exception("ConvertObjectToBytes: Marshal.SizeOf(T) returned a size of 0.");
}
}
return bytes;
}
}
}
回答1:
If you run ildasm.exe on assembly then the cause of the problem becomes clear:
Note the mysterious <FirmwareRevision>k__BackingField
field that got added to the class. You can probably guess where it came from, it is the auto-generated backing field for the automatic property FirmwareRevision. The native code sees the fields, not the properties. Which puts them in the wrong order.
The fix is simple, you need to declare the backing field explicitly and avoid letting the compiler generate it:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class Complex {
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
private byte[] _headerInfo = new byte[5];
private uint firmwareRevision;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 9)]
private byte[] _softwarePartNumber = new byte[9];
public uint FirmwareRevision {
get { return firmwareRevision; }
set { firmwareRevision = value; }
}
//etc..
}
Watch out for Pack = 1, it isn't often correct. It matters in this case since the first field is 5 bytes long. The natural alignment puts firmwareRevision at offset 8, you'll get it at offset 5.
来源:https://stackoverflow.com/questions/7044458/c-sharp-marshalling-question