此需求多用于使用C#调用C语言的库,由于C语言写的程序为了节省内存,通常会使用到位域这种数据结构,而C#中没有对应的位域结构,因此需要用其他的语法模拟出位域的效果。
位段(或称“位域”,Bit field)为一种数据结构,可以把数据以位的形式紧凑的储存,并允许程序员对此结构的位进行操作。相邻的两个位域如果基类型(underlying type)的长度相同,在后的位域适合当前内存分配单元且没有跨内存分配边界,那么这两个位域分配到同一个(1、2或4字节的)分配单元。可以通俗理解为:具有相同的基类型(underlying type)长度的相邻位域尽量装入基类型的同一个对象,如果装得下的话。
上面是一个C语言的头文件声明,此次的需要就是要用C#去建立一个结构体能与C的结构体对应上。我们先看实现。
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class Frame
{
public UInt64 Timestamp; // us
public UInt32 Id;
private UInt16 Flags;//Flags用于存放下列值,并用C#属性来实现位域的功能。
//属性用于访问位域
public UInt16 SendType // 仅发送有效, 接收为0, 0:正常发送, 1:单次发送 2:自发自收
{
get { return (ushort)(Flags & (0x0003)); }
set { Flags = (ushort)((Flags & ~0x0003) | (value & 0x0003)); }
}
public UInt16 Tx // 1:tx 2:rx
{
get { return (ushort)((Flags & (0x0004)) >> 2); }
set { Flags = (ushort)((Flags & ~0x0004) | ((value << 2) & 0x0004)); }
}
public UInt16 Echo
{
get { return (ushort)((Flags & (0x0008)) >> 3); }
set { Flags = (ushort)((Flags & ~0x0008) | ((value << 3) & 0x0008)); }
}
public UInt16 Fd // 1:canfd 2:can
{
get { return (ushort)((Flags & (0x0010)) >> 4); }
set { Flags = (ushort)((Flags & ~0x0010) | ((value << 4) & 0x0010)); }
}
public UInt16 Rtr // 1:remote 0:data frame
{
get { return (ushort)((Flags & (0x0020)) >> 5); }
set { Flags = (ushort)((Flags & ~0x0020) | ((value << 5) & 0x0020)); }
}
public UInt16 Ext // 1:extend 0:standard
{
get { return (ushort)((Flags & (0x0040)) >> 6); }
set { Flags = (ushort)((Flags & ~0x0040) | ((value << 6) & 0x0064)); }
}
public UInt16 Err // 1:error frame 0:normal frame;
{
get { return (ushort)((Flags & (0x080)) >> 7); }
set { Flags = (ushort)((Flags & ~0x0009) | ((value << 7) & 0x0009)); }
}
public UInt16 Brs // 1:canfd加速 0:不加速
{
get { return (ushort)((Flags & (0x0100)) >> 8); }
set { Flags = (ushort)((Flags & ~0x000A) | ((value << 8) & 0x000A)); }
}
public UInt16 Esi // 1:被动错误 0:主动错误
{
get { return (ushort)((Flags & (0x0200)) >> 9); }
set { Flags = (ushort)((Flags & ~0x000B) | ((value << 9) & 0x000B)); }
}
public UInt16 Reserved // 保留
{
get { return (ushort)((Flags & (0x7C00)) >> 10); }
set { Flags = (ushort)((Flags & ~0x7C00) | ((value << 10) & 0x7C00)); }
}
public UInt16 Trigger // 触发位
{
get { return (ushort)((Flags & (0x8000)) >> 15); }
set { Flags = (ushort)((Flags & ~0x8000) | ((value << 15) & 0x8000)); }
}
public byte Channel; // 通道号, 若为-1, 代表该报文是发送给所有的通道
public byte Len;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
public byte[] Data;
}
属性(Property)?是类,结构,接口的成员。提供一个操作其他私有(private)字段的渠道,为了防止用户篡改结构,类中的字段。用于可以自定义get(),set()访问器中操作字段的方法。
此处使用了C#的属性语法,新建一个Flags的16位整型变量用于存放需要使用位域操作的数据,然后新建了不同的属性语法中来访问Flags中对应的各自数据。此处get()访问器中使用了&的位运算符,以求取得各自的有效数据。
以SendType为例,
占16位中的2个位,并且处于最低的两个位,因此有效位为二进制表达为b0000000000000011转成16进制为0x0003.因此用flags按位与0x0003.便能获取flags中最低两个位的数据,然后再用(ushort)强转成16位整型。
在属性中,当我们需要改变类/结构中某个私有字段时,使用set访问器。
(Flags & ~0x0003):首先用Flags与上求反的0x0003,目的是储存数据的无关部分。
(value & 0x0003):用设定值value与上0x0003,得到数据有效部分.
然后用按位或(|),把两个部分的数据叠加起来。
到这里,我们便完成了对其中某一个数据存与取,然后以此类推,就能够完成整个16位整型中所有数据的存取操作。