如何在 C# 中静态模拟 C 位域?
How to emulate statically the C bitfields in c#?
我必须定义一个通信协议,我想使用一个位域来存储一些逻辑值。
我在两个系统上工作:发送方:一个设备和一个 .Net 软件作为接收方。
在固件方面,我通常将位域结构定义为:
struct __attribute__((__packed__)) BitsField
{
// Logic values
uint8_t vesselPresenceSw: 1;
uint8_t drawerPresenceSw: 1;
uint8_t pumpState: 1;
uint8_t waterValveState: 1;
uint8_t steamValveState: 1;
uint8_t motorDriverState: 1;
// Unused
uint8_t unused_0: 1;
uint8_t unused_1: 1;
};
如何在支持字节反序列化的软件端定义相同的结构来构建结构本身?
请看例子,
C代码,
struct example_bit_field
{
unsigned char bit1 : 1;
unsigned char bit2 : 1;
unsigned char two_bits : 2;
unsigned char four_bits : 4;
}
和 C# 等效,
[BitFieldNumberOfBitsAttribute(8)]
struct ExampleBitField : IBitField
{
[BitFieldInfo(0, 1)]
public bool Bit1 { get; set; }
[BitFieldInfo(1, 1)]
public byte Bit2 { get; set; }
[BitFieldInfo(2, 2)]
public byte TwoBits { get; set; }
[BitFieldInfo(4, 4)]
public byte FourBits { get; set; }
}
来源:- https://www.codeproject.com/Articles/1095576/Bit-Field-in-Csharp-using-struct
恐怕没有与 C 样式位域结构等效的直接 C#。
C# 能够在一定程度上通过使用 FieldOffset
属性来近似 C 风格的 unions。这些明确的布局属性允许您指定精确的和可能重叠的字段偏移量。不幸的是,这甚至不能让你做到一半:必须以 字节 而不是位来指定偏移量,并且在读取或写入重叠字段时不能强制执行特定宽度。
最接近原生支持位域的 C# 可能是基于标志的 enum
类型。如果您不需要超过 64 位,您可能会发现这已经足够了。首先根据适合所有标志的最小无符号类型声明 enum
:
[Flags]
public enum BitFields : byte {
None = 0,
VesselPresenceSw = 1 << 0,
DrawerPresenceSw = 1 << 1,
PumpState = 1 << 2,
WaterValveState = 1 << 3,
SteamValveState = 1 << 4,
MotorDriverState = 1 << 5
}
命名的项目可以分配给它们适合基础类型的任何值(在本例中为 byte
),因此如果您愿意,一个项目可以代表多个位。请注意,如果您想直接与 C 风格的位域互操作,您的第一个值应该从 most significant 位开始,而不是 least.
要使用您的标志,只需声明一个新类型的变量或字段并执行您需要的任何按位运算:
BitFields bits = BitFields.None;
bits |= BitFields.VesselPresenceSw | BitFields.PumpState;
bits &= ~BitFields.VesselPresenceSw;
// etc.
从好的方面来说,用 [Flags]
声明的枚举在调试器中显示或转换为字符串时格式很好。例如,如果您要打印表达式 BitFields.VesselPresenceSw | BitFields.PumpState
,您将得到文本 DrawerPresenceSw, PumpState
.
有一个警告:enum
的存储将接受适合基础类型的任何值。这样写是完全合法的:
BitFields badBits = (BitFields)0xFF;
这设置了 byte
大小的枚举的所有 8 位,但我们的命名值仅涵盖 6 位。根据您的要求,您可能希望声明一个仅包含 'legal' 标志的常量,您可以 &
反对它。
如果您需要比这更丰富的内容,可以使用名为 BitArray 的框架级 'bitfield' 数据结构。但是,BitArray
是一种引用类型,它使用托管的 int[]
进行存储。如果你想要一个可以用于互操作目的或任何类型的内存映射的 struct
,它不会帮助你。
你可以试试模仿这样的struct
。看来,您想在 interop 中使用它(例如,C
例程与 C#
程序交换数据)。由于您有 logic 值,因此将它们公开为 bool
:
using System.Runtime.InteropServices;
...
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct MyBitsField {
private Byte m_Data; // We actually store Byte
public MyBitsField(Byte data) {
m_Data = data;
}
private bool GetBit(int index) {
return (m_Data & (1 << index)) != 0;
}
private void SetBit(int index, bool value) {
byte v = (byte)(1 << index);
if (value)
m_Data |= v;
else
m_Data = (byte) ((m_Data | v) ^ v);
}
public bool vesselPresenceSw {
get { return GetBit(0); }
set { SetBit(0, value); }
}
...
public bool motorDriverState {
get { return GetBit(5); }
set { SetBit(5, value); }
}
}
用法:
var itemToSend = new MyBitsField() {
vesselPresenceSw = false,
motorDriverState = true,
};
同时,我也有类似的想法@Dmitry。
我使用 FieldOffset
属性找到了以下解决方案。
无需额外代码即可正常运行。我觉得可以接受。
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct LiveDataBitField
{
// Where the values are effectively stored
public byte WholeField { get; private set; }
public bool VesselPresenceSw
{
get => (WholeField & 0x1) > 0;
set
{
if (value)
{
WholeField |= 1;
}
else
{
WholeField &= 0xfE;
}
}
}
public bool DrawerPresenceSw
{
get => (WholeField & 0x2) >> 1 > 0;
set
{
if (value)
{
WholeField |= (1 << 1);
}
else
{
WholeField &= 0xFD;
}
}
}
public bool PumpState
{
get => (WholeField & 0x4) >> 2 > 0;
set
{
if (value)
{
WholeField |= (1 << 2);
}
else
{
WholeField &= 0xFB;
}
}
}
public bool WaterValveState
{
get => (WholeField & 0x8) >> 3 > 0;
set
{
if (value)
{
WholeField |= (1 << 3);
}
else
{
WholeField &= 0xF7;
}
}
}
public bool SteamValveState
{
get => (WholeField & 0x10) >> 4 > 0;
set
{
if (value)
{
WholeField |= (1 << 4);
}
else
{
WholeField &= 0xEF;
}
}
}
public bool MotorDriverState
{
get => (WholeField & 0x20) >> 5 > 0;
set
{
if (value)
{
WholeField |= (1 << 5);
}
else
{
WholeField &= 0xDF;
}
}
}
}
要将字节数组反序列化为结构,您可以使用:
public static object ReadStruct(byte[] data, Type type)
{
var pinnedPacket = GCHandle.Alloc(data, GCHandleType.Pinned);
var obj = Marshal.PtrToStructure(pinnedPacket.AddrOfPinnedObject(), type);
pinnedPacket.Free();
return obj;
}
我必须定义一个通信协议,我想使用一个位域来存储一些逻辑值。
我在两个系统上工作:发送方:一个设备和一个 .Net 软件作为接收方。
在固件方面,我通常将位域结构定义为:
struct __attribute__((__packed__)) BitsField
{
// Logic values
uint8_t vesselPresenceSw: 1;
uint8_t drawerPresenceSw: 1;
uint8_t pumpState: 1;
uint8_t waterValveState: 1;
uint8_t steamValveState: 1;
uint8_t motorDriverState: 1;
// Unused
uint8_t unused_0: 1;
uint8_t unused_1: 1;
};
如何在支持字节反序列化的软件端定义相同的结构来构建结构本身?
请看例子,
C代码,
struct example_bit_field
{
unsigned char bit1 : 1;
unsigned char bit2 : 1;
unsigned char two_bits : 2;
unsigned char four_bits : 4;
}
和 C# 等效,
[BitFieldNumberOfBitsAttribute(8)]
struct ExampleBitField : IBitField
{
[BitFieldInfo(0, 1)]
public bool Bit1 { get; set; }
[BitFieldInfo(1, 1)]
public byte Bit2 { get; set; }
[BitFieldInfo(2, 2)]
public byte TwoBits { get; set; }
[BitFieldInfo(4, 4)]
public byte FourBits { get; set; }
}
来源:- https://www.codeproject.com/Articles/1095576/Bit-Field-in-Csharp-using-struct
恐怕没有与 C 样式位域结构等效的直接 C#。
C# 能够在一定程度上通过使用 FieldOffset
属性来近似 C 风格的 unions。这些明确的布局属性允许您指定精确的和可能重叠的字段偏移量。不幸的是,这甚至不能让你做到一半:必须以 字节 而不是位来指定偏移量,并且在读取或写入重叠字段时不能强制执行特定宽度。
最接近原生支持位域的 C# 可能是基于标志的 enum
类型。如果您不需要超过 64 位,您可能会发现这已经足够了。首先根据适合所有标志的最小无符号类型声明 enum
:
[Flags]
public enum BitFields : byte {
None = 0,
VesselPresenceSw = 1 << 0,
DrawerPresenceSw = 1 << 1,
PumpState = 1 << 2,
WaterValveState = 1 << 3,
SteamValveState = 1 << 4,
MotorDriverState = 1 << 5
}
命名的项目可以分配给它们适合基础类型的任何值(在本例中为 byte
),因此如果您愿意,一个项目可以代表多个位。请注意,如果您想直接与 C 风格的位域互操作,您的第一个值应该从 most significant 位开始,而不是 least.
要使用您的标志,只需声明一个新类型的变量或字段并执行您需要的任何按位运算:
BitFields bits = BitFields.None;
bits |= BitFields.VesselPresenceSw | BitFields.PumpState;
bits &= ~BitFields.VesselPresenceSw;
// etc.
从好的方面来说,用 [Flags]
声明的枚举在调试器中显示或转换为字符串时格式很好。例如,如果您要打印表达式 BitFields.VesselPresenceSw | BitFields.PumpState
,您将得到文本 DrawerPresenceSw, PumpState
.
有一个警告:enum
的存储将接受适合基础类型的任何值。这样写是完全合法的:
BitFields badBits = (BitFields)0xFF;
这设置了 byte
大小的枚举的所有 8 位,但我们的命名值仅涵盖 6 位。根据您的要求,您可能希望声明一个仅包含 'legal' 标志的常量,您可以 &
反对它。
如果您需要比这更丰富的内容,可以使用名为 BitArray 的框架级 'bitfield' 数据结构。但是,BitArray
是一种引用类型,它使用托管的 int[]
进行存储。如果你想要一个可以用于互操作目的或任何类型的内存映射的 struct
,它不会帮助你。
你可以试试模仿这样的struct
。看来,您想在 interop 中使用它(例如,C
例程与 C#
程序交换数据)。由于您有 logic 值,因此将它们公开为 bool
:
using System.Runtime.InteropServices;
...
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct MyBitsField {
private Byte m_Data; // We actually store Byte
public MyBitsField(Byte data) {
m_Data = data;
}
private bool GetBit(int index) {
return (m_Data & (1 << index)) != 0;
}
private void SetBit(int index, bool value) {
byte v = (byte)(1 << index);
if (value)
m_Data |= v;
else
m_Data = (byte) ((m_Data | v) ^ v);
}
public bool vesselPresenceSw {
get { return GetBit(0); }
set { SetBit(0, value); }
}
...
public bool motorDriverState {
get { return GetBit(5); }
set { SetBit(5, value); }
}
}
用法:
var itemToSend = new MyBitsField() {
vesselPresenceSw = false,
motorDriverState = true,
};
同时,我也有类似的想法@Dmitry。
我使用 FieldOffset
属性找到了以下解决方案。
无需额外代码即可正常运行。我觉得可以接受。
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct LiveDataBitField
{
// Where the values are effectively stored
public byte WholeField { get; private set; }
public bool VesselPresenceSw
{
get => (WholeField & 0x1) > 0;
set
{
if (value)
{
WholeField |= 1;
}
else
{
WholeField &= 0xfE;
}
}
}
public bool DrawerPresenceSw
{
get => (WholeField & 0x2) >> 1 > 0;
set
{
if (value)
{
WholeField |= (1 << 1);
}
else
{
WholeField &= 0xFD;
}
}
}
public bool PumpState
{
get => (WholeField & 0x4) >> 2 > 0;
set
{
if (value)
{
WholeField |= (1 << 2);
}
else
{
WholeField &= 0xFB;
}
}
}
public bool WaterValveState
{
get => (WholeField & 0x8) >> 3 > 0;
set
{
if (value)
{
WholeField |= (1 << 3);
}
else
{
WholeField &= 0xF7;
}
}
}
public bool SteamValveState
{
get => (WholeField & 0x10) >> 4 > 0;
set
{
if (value)
{
WholeField |= (1 << 4);
}
else
{
WholeField &= 0xEF;
}
}
}
public bool MotorDriverState
{
get => (WholeField & 0x20) >> 5 > 0;
set
{
if (value)
{
WholeField |= (1 << 5);
}
else
{
WholeField &= 0xDF;
}
}
}
}
要将字节数组反序列化为结构,您可以使用:
public static object ReadStruct(byte[] data, Type type)
{
var pinnedPacket = GCHandle.Alloc(data, GCHandleType.Pinned);
var obj = Marshal.PtrToStructure(pinnedPacket.AddrOfPinnedObject(), type);
pinnedPacket.Free();
return obj;
}