You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#使用MemoryMarshal.Cast转字节数组到结构体报错解决方案

问题原因

MemoryMarshal.Cast<TFrom, TTo> 仅支持完全不包含引用、指针的非托管值类型做转换。你定义的结构体里MarshalZone[]、WeatherForecastSample[]属于C#托管数组,本质是引用类型——哪怕加了[MarshalAs(UnmanagedType.ByValArray)]特性,这个特性也只在传统互操作封送场景生效,CLR层面数组字段仍然是引用,因此会抛出Only value types without pointers or references are supported异常。

另外你当前结构体设置Pack = 0是错误配置:F1 22 UDP遥测包采用1字节紧凑对齐,Pack=0会使用平台默认对齐规则(Windows环境通常为8字节对齐),会导致字段内存偏移和实际数据包不匹配,解析出的数据会完全错误,必须改为Pack=1。


可行解决方案

方案1:零拷贝高性能方案(推荐,适配UDP高频数据包场景)

将结构体改为纯非托管值类型,无需封送、无内存拷贝,解析性能最高,支持直接使用MemoryMarshal.Cast。

.NET 7及以上版本实现

使用InlineArray特性定义定长值类型数组,不需要unsafe代码:

  1. 先定义对应长度的内联数组结构体:
using System.Runtime.CompilerServices;

[InlineArray(21)]
public struct MarshalZoneArray21
{
    public MarshalZone Item;
}

[InlineArray(56)]
public struct WeatherForecastArray56
{
    public WeatherForecastSample Item;
}
  1. 修改原PacketSessionData结构体,替换托管数组字段,移除无效的MarshalAs特性,修正Pack值:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct PacketSessionData
{
    public PacketHeader m_header;
    public byte m_weather;
    public sbyte m_trackTemperature;
    public sbyte m_airTemperature;
    public byte m_totalLaps;
    public ushort m_trackLength;
    public byte m_sessionType;
    public sbyte m_trackId;
    public byte m_formula;
    public ushort m_sessionTimeLeft;
    public ushort m_sessionDuration;
    public byte m_pitSpeedLimit;
    public byte m_gamePaused;
    public byte m_isSpectating;
    public byte m_spectatorCarIndex;
    public byte m_sliProNativeSupport;
    public byte m_numMarshalZones;
    public MarshalZoneArray21 m_marshalZones;
    public byte m_safetyCarStatus;
    public byte m_networkGame;
    public byte m_numWeatherForecastSamples;
    public WeatherForecastArray56 m_weatherForecastSamples;
    public byte m_forecastAccuracy;
    public byte m_aiDifficulty;
    public uint m_seasonLinkIdentifier;
    public uint m_weekendLinkIdentifier;
    public uint m_sessionLinkIdentifier;
    public byte m_pitStopWindowIdealLap;
    public byte m_pitStopWindowLatestLap;
    public byte m_pitStopRejoinPosition;
    public byte m_steeringAssist;
    public byte m_brakingAssist;
    public byte m_gearboxAssist;
    public byte m_pitAssist;
    public byte m_pitReleaseAssist;
    public byte m_ERSAssist;
    public byte m_DRSAssist;
    public byte m_dynamicRacingLine;
    public byte m_dynamicRacingLineType;
    public byte m_gameMode;
    public byte m_ruleSet;
    public uint m_timeOfDay;
    public byte m_sessionLength;
}

修改完成后直接调用原代码即可正常转换:

var telemetry = MemoryMarshal.Cast<byte, PacketSessionData>(bytes)[0];

内联数组可以像普通数组一样直接通过索引访问元素。

.NET 6及更低版本实现

如果使用旧版本.NET,可以用unsafe固定缓冲区实现同样的零拷贝效果:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct PacketSessionData
{
    // 其余字段和上面一致,仅替换两个数组字段
    public byte m_numMarshalZones;
    public fixed byte m_marshalZones[21 * 5]; // 单MarshalZone大小为5字节
    public byte m_safetyCarStatus;
    public byte m_networkGame;
    public byte m_numWeatherForecastSamples;
    public fixed byte m_weatherForecastSamples[56 * 8]; // 单WeatherForecastSample大小为8字节
    // 其余字段和上面一致
}

读取数组元素时通过内存读取即可:

public static unsafe MarshalZone GetMarshalZone(ref PacketSessionData data, int index)
{
    if (index < 0 || index >= 21) throw new ArgumentOutOfRangeException(nameof(index));
    fixed (byte* ptr = data.m_marshalZones)
    {
        return MemoryMarshal.Read<MarshalZone>(new Span<byte>(ptr + index * 5, 5));
    }
}

方案2:封送转换方案(无需修改结构体,性能较低)

如果不想修改现有结构体定义,可以使用Marshal.PtrToStructure做封送转换,该方法会识别MarshalAs特性自动完成定长数组的转换,但每次转换会产生内存拷贝和封送开销,不适合高频UDP数据包场景:

PacketSessionData telemetry;
fixed (byte* pBytes = bytes)
{
    telemetry = Marshal.PtrToStructure<PacketSessionData>((IntPtr)pBytes);
}

注意使用该方案同样需要把结构体的Pack值改为1,否则字段偏移错误会导致解析失败。


内容的提问来源于stack exchange,提问作者J.Meulenbeld

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 20:03:24