The way to represent the reserved bits is through the [StructLayout] and [FieldOffset] attributes applied to to the fields of a struct. However ... this union has several fields that are less than a byte, so I'd recommend not doing that at all because [FieldOffset] is in units of whole bytes, so you're not buying yourself anything by trying to replicate this union.
If you're going to marshal a struct, then just use primitives of the appropriate size and write code to get at the underlying values.
[StructLayout(LayoutKind.Sequential)] public struct ReportManaged { // the actual fields of the struct private readonly uint SnpVersion; private readonly uint SnpGuestSvn; private readonly ulong TunePolicyUnion; private readonly ulong ChannelPolicyUnion; private fixed byte[48] PolicyDigest; // properties to access the struct's data public ulong ModelVer => TunePolicy; public byte TuneConfig_AbiMinor => //most significant byte of TunePolicyUnion public byte TuneConfig_AbiMajor => //2nd most significant byte of TunePolicyUnion public bool TuneConfig_TTAllowed => //17th bit of TunePolicyUnion public bool TuneConfig_RvrdTrue => //18th bit of TunePolicyUnion public bool TuneConfig_MigrateTTAllowed => //19th bit of TunePolicyUnion public bool TuneConfig_DebugAllowed => //20th bit of TunePolicyUnion public bool TuneConfig_RrvdFalse => //rest of TunePolicyUnion public ulong ChannelPolicy_AsUInt64 => ChannelPolicyUnion; public bool ChannelConfig_TTEnabled => //most significant byte of ChannelPolicyUnion public ulong ChannelConfig_RRvd => //rest of ChannelPolicyUnion }
Another option, and the one I prefer to use myself for this sort of thing, is to marshal the Report over simply as a byte array then read it like it's a stream, building a C# object as you go along. The union defines a message format. Each message starts with a 32-bit SnpVersion and a 32-bit SnpGuestSvn, and then it has either a 64-bit Model Version or a Tune Config, and then a 64-bit field or a ChannelConfig, and it always ends with 48 bytes of data.