5

我知道为了在 C# 中表示联合,我需要使用 StructLayout[LayoutKind.Explicit)] 和 [FieldOffset(x)] 属性来指定联合内的字节偏移量。但是,我有一个我想要表示的以下联合,并且 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 函数,并且其中一个数据结构被定义为这样,我对如何最好地表示这个联合结构没有想法。

4

3 回答 3

4

那里不需要工会;数据的一个字段+属性,8个执行按位“移位”操作的属性,例如:

public uint Value {get;set;}

public uint Flag2 {
   get { return Value >> 2; }
}

等等。我还以为你想要布尔在这里?

通常我会说:不要制作可变结构。PInvoke可能(我不确定)是一个有效的场景,所以我会忽略它:)

如果该值确实使用超过 32 位,请考虑将支持字段切换为 ulong。

于 2011-01-28T17:15:37.570 回答
2

是的,你可以这样做。您在正确的道路上,答案在于使用BitVector32以及FieldOffsetStructLayout属性。但是,在执行此操作时需要牢记一些事项:

  1. 您需要明确指定将包含相关数据的变量的大小。第一项非常重要,需要注意。例如,在您上面的问题中,您将info指定为unsigned intunsigned int的大小是多少?32位?64位?这取决于运行此特定 .NET 版本的操作系统版本(可能是 .NET Core、Mono 或 Win32/Win64)。

  2. 这是什么“字节顺序”或位顺序?同样,我们可能在任何类型的硬件上运行(想想 Mobile/Xamarin,而不仅仅是笔记本电脑或平板电脑)——因此,您不能假设 Intel 位顺序。

  3. 我们希望避免任何依赖于语言的内存管理,或者 C/C++ 术语中的 POD(普通旧数据)类型。这意味着只坚持值类型。

我将根据您的问题和标志 0-31 的规范做出sizeof(int) == 32的假设。

诀窍是确保以下内容:

  1. 所有数据都是字节对齐的。
  2. 位域和信息域在同一字节边界上对齐。

以下是我们如何做到这一点:

[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
}

关于这个主题的最后一句话......很明显,只有在你绝对必须这样做的情况下才应该这样做。显然,这需要对您的操作系统环境、您运行的语言、您的调用约定以及许多其他脆弱的要求有专门的了解。

在任何其他情况下,这里都有太多代码气味,这显然是不可移植性的尖叫。但是从您的问题的上下文来看,我推测重点是您需要在硬件附近运行并且需要这种精度。

买者自负!

于 2018-11-24T06:30:05.767 回答
2

最优雅的解决方案是使用标志枚举

最好使用 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 封送

于 2018-11-24T19:01:16.480 回答