如何将C#字节数组转换为结构化数据?

7

我正在通过USB将64字节的数据包传输到微控制器。在微控制器的C代码中,这些数据包具有以下结构:

typedef union
{
    unsigned char data[CMD_SIZE];
    cmd_get_t get;
    // plus more union options
} cmd_t;

使用

typedef struct
{
    unsigned char cmd;          //!< Command ID
    unsigned char id;           //!< Packet ID
    unsigned char get_id;       //!< Get identifier
    unsigned char rfu[3];       //!< Reserved for future use
    union
    {
        unsigned char data[58];     //!< Generic data
        cmd_get_adc_t adc;          //!< ADC data
        // plus more union options
    } data;                     //!< Response data
} cmd_get_t;

并且

typedef struct
{
    int16_t supply;
    int16_t current[4];
} cmd_get_adc_t;

在C#的PC端,我已经提供了一个返回64字节数据包作为Byte[]的函数。该函数使用Marshal.Copy将接收到的数据复制到Byte[]数组中。然后,我使用了一个格式为结构体的C#结构:
[StructLayout(LayoutKind.Sequential, Pack=1)]
    public struct COMMAND_GET_ADC
    {
        public byte CommandID;
        public byte PacketID;
        public byte GetID;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst=3)]
            public byte[] RFU;
        public short Supply;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]
            public short[] Current;
    }

我再次使用Marshal.Copy将字节数组复制到结构体中,以便可以按照结构化数据的方式进行操作,例如:

COMMAND_GET_ADC cmd = (COMMAND_GET_ADC)RawDeserialize(INBuffer, 1, typeof(COMMAND_GET_ADC));
short supply = cmd.Supply;

使用

public static object RawDeserialize(Byte[] rawData, int position, Type anyType)
{
    int rawsize = Marshal.SizeOf(anyType);
    if(rawsize > rawData.Length)
    {
        return null;
    }
    IntPtr buffer = Marshal.AllocHGlobal(rawsize);
    Marshal.Copy(rawData, position, buffer, rawsize);
    object retobj = Marshal.PtrToStructure(buffer, anyType);
    Marshal.FreeHGlobal(buffer);
    return retobj;
}

这种方法感觉就像是在复制数据,可能并不是最有效的实现方式。我还需要将结构化数据转换回字节数组以便向设备发送指令。我有一个使用相同过程的方法(即使用结构体,然后将其序列化为字节数组并将字节数组传递给写入函数)。

是否有更好的替代方案?

2个回答

2

如果您可以使用不安全代码,您可以使用“fixed”关键字将字节数组转换为指向您的结构体的指针。


在我正在工作的代码库中,该类被声明为不安全(以允许与Win32 USB HID dll进行交互)。所以是的,我想我可以这样做。我只需要弄清楚不安全的含义和危害。我一直避免使用它,因为我还没有完全理解它。 - Duncan Drennan

2
如果您自己调用本地dll,可以定义您的DllImport-s,使它们直接返回和接受COMMAND_GET_ADC - 前提是您已经正确表示结构体。框架本身应该会处理它。
如果您必须使用由向您提供的方法强制执行的字节数组,则我不知道,我从未遇到过这样的约束。我总是尝试以与本地dll相同的方式表示我的互操作数据,我不记得我在这方面遇到过重大问题。
编辑:
[StructLayout(LayoutKind.Explicit)]
public struct COMMAND_GET
{
    [FieldOffset(0)]
    public byte CommandID;
    [FieldOffset(1)]
    public byte PacketID;
    [FieldOffset(2)]
    public byte GetID;
    [FieldOffset(3)]
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=3)]
    public byte[] RFU;
    [FieldOffset(6)]
    public ADC_Data Adc_data;
    [FieldOffset(6)]
    public SomeOther_Data other_data;
    [FieldOffset(6)]
    ....
}


[StructLayout(LayoutKind.Sequential, Pack=1)]
public struct ADC_Data
{
    public short Supply;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]
    public short[] Current;
}

基本上,您在FieldOffset(6)处创建了类似于cmd_get_t中的联合数据联合。

我并不完全明白你的意思。我需要先检查第一个字节(命令 ID),然后将数据映射到适当的结构体中。你提出的解决方案如何与此相配合?你能够给出一些简单的示例(或者指向一个)吗? - Duncan Drennan
1
我实际上尝试过这个,但由于LayoutKind设置为explicit并且我对数组使用了MarshalAs,线程似乎会崩溃。我还没有弄清楚原因。感谢提供的示例。 - Duncan Drennan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接