使用 c# 的 StrucLayout 和 FieldOffset 表示联合位域

发布于 2024-10-14 16:18:07 字数 688 浏览 4 评论 0原文

据我所知,为了在 C# 中表示联合,我需要使用 StructLayout[LayoutKind.Explicit)] 和 [FieldOffset(x)] 属性来指定联合内的字节偏移量。但是,我有一个想要表示的以下联合,并且 FieldOffset attrib 仅按字节大小偏移。

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 函数,并且其中一个数据结构是这样定义的,但我对如何最好地表示这个联合结构没有想法。

I understand that in order to represent unions in C# I need to use StructLayout[LayoutKind.Explicit)] and [FieldOffset(x)] attribut to specify the byte offset inside the union. However, I have a following union I want to represent and FieldOffset attrib only offset by size of a byte.

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

As you can see for the inner struct in the union, I can't use FieldOffset since I need something that can offset by a bit.

Is there a solution to this? I am trying to call a DLL function and one of the data struct was defined as such and I ran out of ideas on how to best represent this union struct.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

轻拂→两袖风尘 2024-10-21 16:18:07

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

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

  2. 这是什么“字节顺序”或位顺序?同样,我们可能在任何类型的硬件上运行(想想移动/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
}

关于此主题的最后一句话...很明显,只有当您绝对必须 这样做。显然,这需要对操作系统环境、运行语言、调用约定以及许多其他脆弱要求的专业知识。

在任何其他上下文中,这里都有如此多的代码味道,这显然是不可移植性的。但从你的问题的背景来看,我推测重点是你需要靠近硬件运行并且需要这种精度。

买者自负!

Yes, you can do this. You are on the right path, the answer lies in the use of BitVector32 along with the FieldOffset and StructLayout attributes. However, there are a number of things you need to keep in mind when doing this:

  1. You need to explicitly specify the size of the variables that will contain the data in question. This first item is very important to pay attention to. In your question above, for example, you specify info as unsigned int. What size is unsigned int? 32 bits? 64 bits? That depends on the version of the OS where this particular version of .NET is running (this may be .NET Core, Mono, or Win32/Win64).

  2. What 'endianness ' or bit order is this? Again, we may be running on any type of hardware (think Mobile/Xamarin, not just laptop or tablet) -- as such, you cannot assume Intel bit order.

  3. We will want to avoid any memory management that is language dependent, or in C/C++ parlance POD (plain old data) types. That will mean sticking to value types only.

I'm going to make the assumption, based on your question and the specification of flags 0-31, that sizeof(int) == 32.

The trick then is to ensure the following:

  1. All data is byte aligned.
  2. The bitfields and info field align on the same byte boundary.

Here is how we accomplish that:

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

Pay particular attention to the constructor. By definition, you cannot define default constructor for structs in C#. However, we need some way to ensure that the BitVector32 object and its sections are initialized properly before use. We accomplish that by requiring a constructor that takes a dummy integer parameter, and initialize the object like this:

    /// <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
        };

By the way, you aren't by any means limited to single bitfields -- you can define any size bitfield you like. For example, if I were to change your example to be:

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

I would just change my class to be:

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

One last word about this topic... it should be obvious that this should only be done if you absolutely MUST do this. Clearly, this requires specialized knowledge of your OS environment, the language you are running in, your calling convention, and a host of other brittle requirements.

In any other context there are so many code smells here that this clearly screams of non-portability. But from the context of your question, I would surmise the whole point is that you need to run close to the hardware and need this kind of precision.

Caveat emptor!

网名女生简单气质 2024-10-21 16:18:07

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

public uint Value {get;set;}

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

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

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

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

No need for union there; one field+property for the data, 8 properties that do bitwise "shift" operations, for example:

public uint Value {get;set;}

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

etc. I would also have thought you want bool here?

Normally I'd say: don't make mutable structs. PInvoke may (I'm not sure) be a valid scenario for that, so I'll ignore it :)

If the value is genuinely using more than 32 bits, consider switching the backing field to ulong.

迷爱 2024-10-21 16:18:07

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

更好地使用 uint 而不是 int

[Flags]
public enum MyEnum
    : uint
{
    None=0,
    Flag1=1,
    Flag2=1<<1,
    Flag3=1<<2,
    // etc
    Flag32=1<<31
}

之后,您可以使用 int 作为 enum 和 uint

MyEnum value=MyEnum.Flag1|MyEnum.Flag2;
uint uintValue=(uint)value;
// uintValue=3

这通常由 PInvoke 封送

Most elegant solution for this is using flags enum

Better use uint instead int

[Flags]
public enum MyEnum
    : uint
{
    None=0,
    Flag1=1,
    Flag2=1<<1,
    Flag3=1<<2,
    // etc
    Flag32=1<<31
}

After it you can use int as enum and as uint

MyEnum value=MyEnum.Flag1|MyEnum.Flag2;
uint uintValue=(uint)value;
// uintValue=3

This will be normally marshal by PInvoke

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文