C#编组问题 [英] C# Marshalling question

查看:85
本文介绍了C#编组问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

基于svick的建议,我相信我可以大大简化我的帖子和问题.下面是一些完整的代码,演示了我的问题,即将字节编组到结构中无法按我期望的方式工作... 对于一个对象,其中两个数组被另一个原语分开的情况,我的对象没有按照我期望的方式编组为字节.尽管我指定了"Sequential",但两个byte []数组首先放在字节数组中,然后是uint.发生了什么事? 在现实生活"中,我正在处理其他人的二进制文件,其中的数据实际上按照字节[5] firstArray,uint firmwareVersion和字节[9] secondArray的顺序排列.

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




}

}

推荐答案

如果在程序集上运行ildasm.exe,则问题的原因将变得清楚:

If you run ildasm.exe on assembly then the cause of the problem becomes clear:

请注意已添加到类中的神秘的<FirmwareRevision>k__BackingField字段.您可能会猜到它的来源,它是自动属性FirmwareRevision的自动生成的后备字段.本机代码看到的是字段,而不是属性.这会使它们的顺序错误.

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

当心Pack = 1,这通常不正确.在这种情况下很重要,因为第一个字段的长度为5个字节.自然对齐会将firmwareRevision放在偏移量8处,您将在偏移量5处得到.

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.

这篇关于C#编组问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆