据我所知,为了在C#中表示联合,我需要使用StructLayout [LayoutKind.Explicit]]和[FieldOffset(x)] attribut来指定联合内部的字节偏移量。但是,我有一个我想表示的跟随联合,而FieldOffset属性只能偏移一个字节的大小。
union _myUnion
{
unsigned int info;
struct
{
unsigned int flag1:1 // bit 0
unsigned int flag2:1 // bit 1
unsigned int flag3:1 // bit 2
unsigned int flag4:1 // bit 3
unsigned int flag5:1 // bit 4
unsigned int flag6:1 // bit 5
.
.
.
unsigned int flag31:1 // bit 31
}
}
正如你在联合中看到的内部结构一样,我不能使用FieldOffset,因为我需要一些可以稍微偏移的东西。
有解决方案吗?我试图调用一个DLL函数,其中一个数据结构被定义为如此,我没有关于如何最好地表示这个联合结构的想法。
答案 0 :(得分:4)
不需要在那里结合;一个字段+数据属性,8个按位“移位”操作的属性,例如:
public uint Value {get;set;}
public uint Flag2 {
get { return Value >> 2; }
}
等。我还以为你想要布尔这里?
通常我会说:不要制作可变的结构。 PInvoke 可能(我不确定)是一个有效的场景,所以我会忽略它:)
如果值真的使用超过32位,请考虑将支持字段切换为ulong。
答案 1 :(得分:1)
是的,您可以这样做。您在正确的道路上,答案在于使用 BitVector32 以及 FieldOffset 和 StructLayout 属性。但是,在执行此操作时,需要记住许多事项:
您需要明确指定将包含相关数据的变量的大小。第一项非常重要,要注意。例如,在上述问题中,您将 info 指定为 unsigned int 。 unsigned int 是什么大小? 32位? 64位?这取决于运行特定版本.NET的操作系统版本(可能是.NET Core,Mono或Win32 / Win64)。
这是什么“字节序”或位顺序?同样,我们可能正在任何类型的硬件上运行(想想Mobile / Xamarin,而不仅仅是笔记本电脑或平板电脑),因此,您不能假设Intel的位顺序。
我们将要避免依赖于语言的任何内存管理,或者避免使用C / C ++用语POD(普通旧数据)类型。那将意味着只坚持值类型。
根据您的问题和标志0-31的说明,我将做出 sizeof(int)== 32 的假设。
然后,诀窍是确保以下各项:
这是我们如何做到这一点:
[StructLayout(LayoutKind.Explicit, Size = 1, CharSet = CharSet.Ansi)]
public struct MyUnion
{
#region Lifetime
/// <summary>
/// Ctor
/// </summary>
/// <param name="foo"></param>
public MyUnion(int foo)
{
// allocate the bitfield
info = new BitVector32(0);
// initialize bitfield sections
flag1 = BitVector32.CreateSection(1);
flag2 = BitVector32.CreateSection(1, flag1);
flag3 = BitVector32.CreateSection(1, flag2);
}
#endregion
#region Bifield
// Creates and initializes a BitVector32.
[FieldOffset(0)]
private BitVector32 info;
#endregion
#region Bitfield sections
/// <summary>
/// Section - Flag 1
/// </summary>
private static BitVector32.Section flag1;
/// <summary>
/// Section - Flag 2
/// </summary>
private static BitVector32.Section flag2;
/// <summary>
/// Section - Flag 3
/// </summary>
private static BitVector32.Section flag3;
#endregion
#region Properties
/// <summary>
/// Flag 1
/// </summary>
public bool Flag1
{
get { return info[flag1] != 0; }
set { info[flag1] = value ? 1 : 0; }
}
/// <summary>
/// Flag 2
/// </summary>
public bool Flag2
{
get { return info[flag2] != 0; }
set { info[flag2] = value ? 1 : 0; }
}
/// <summary>
/// Flag 1
/// </summary>
public bool Flag3
{
get { return info[flag3] != 0; }
set { info[flag3] = value ? 1 : 0; }
}
#endregion
#region ToString
/// <summary>
/// Allows us to represent this in human readable form
/// </summary>
/// <returns></returns>
public override string ToString()
{
return $"Name: {nameof(MyUnion)}{Environment.NewLine}Flag1: {Flag1}: Flag2: {Flag2} Flag3: {Flag3} {Environment.NewLine}BitVector32: {info}{Environment.NewLine}";
}
#endregion
}
要特别注意构造函数。根据定义,您不能为C#中的结构定义默认构造函数。但是,我们需要某种方法来确保在使用前正确初始化 BitVector32 对象及其部分。为此,我们需要一个构造函数,该构造函数需要一个伪整数参数,然后像这样初始化对象:
/// <summary>
/// Main entry point
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
// brew up one of these...
var myUnion = new MyUnion(0)
{
Flag2 = true
};
顺便说一句,您绝不限于单个位域-您可以定义任意大小的位域。例如,如果我要将您的示例更改为:
union _myUnion
{
unsigned int info;
struct
{
unsigned int flag1 : 3 // bit 0-2
unsigned int flag2 : 1 // bit 3
unsigned int flag3 : 4 // bit 4-7
.
.
.
unsigned int flag31 : 1 // bit 31
}
}
我只是将班级更改为:
[StructLayout(LayoutKind.Explicit, Size = 1, CharSet = CharSet.Ansi)]
public struct MyUnion2
{
#region Lifetime
/// <summary>
/// Ctor
/// </summary>
/// <param name="foo"></param>
public MyUnion2(int foo)
{
// allocate the bitfield
info = new BitVector32(0);
// initialize bitfield sections
flag1 = BitVector32.CreateSection(0x07);
flag2 = BitVector32.CreateSection(1, flag1);
flag3 = BitVector32.CreateSection(0x0f, flag2);
}
#endregion
#region Bifield
// Creates and initializes a BitVector32.
[FieldOffset(0)]
private BitVector32 info;
#endregion
#region Bitfield sections
/// <summary>
/// Section - Flag1
/// </summary>
private static BitVector32.Section flag1;
/// <summary>
/// Section - Flag2
/// </summary>
private static BitVector32.Section flag2;
/// <summary>
/// Section - Flag3
/// </summary>
private static BitVector32.Section flag3;
#endregion
#region Properties
/// <summary>
/// Flag 1
/// </summary>
public int Flag1
{
get { return info[flag1]; }
set { info[flag1] = value; }
}
/// <summary>
/// Flag 2
/// </summary>
public bool Flag2
{
get { return info[flag2] != 0; }
set { info[flag2] = value ? 1 : 0; }
}
/// <summary>
/// Flag 1
/// </summary>
public int Flag3
{
get { return info[flag3]; }
set { info[flag3] = value; }
}
#endregion
#region ToString
/// <summary>
/// Allows us to represent this in human readable form
/// </summary>
/// <returns></returns>
public override string ToString()
{
return $"Name: {nameof(MyUnion2)}{Environment.NewLine}Flag1: {Flag1}: Flag2: {Flag2} Flag3: {Flag3} {Environment.NewLine}BitVector32: {info}{Environment.NewLine}";
}
#endregion
}
关于此主题的最后一句话...很明显,只有在您绝对必须这样做时,才应这样做。显然,这需要您对OS环境,运行语言,调用约定以及许多其他 brittle 要求有专门的知识。
在任何其他情况下,这里都有太多的代码味道,以至于明显地发出了不可移植的声音。但是从您的问题的角度来看,我想得出的全部结论是,您需要靠近硬件运行并且需要这种精度。
偷闲者!
答案 2 :(得分:1)
最优雅的解决方案是使用标志枚举
最好用uint代替int
[Flags]
public enum MyEnum
: uint
{
None=0,
Flag1=1,
Flag2=1<<1,
Flag3=1<<2,
// etc
Flag32=1<<31
}
之后,您可以将int用作枚举和uint
MyEnum value=MyEnum.Flag1|MyEnum.Flag2;
uint uintValue=(uint)value;
// uintValue=3
通常由PInvoke封送