5

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.

Ian Sampson
  • 51
  • 1
  • 2

3 Answers3

4

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.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
3

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!

Stacy Dudovitz
  • 952
  • 9
  • 11
  • Why are you not making the `BitVector32.Section`'s static? is there a need to make them part of the struct's instance? – Scott Chamberlain Nov 24 '18 at 19:18
  • The carry information that is instance to the struct. If you make them static, and instantiate more than one instance, you are now stepping on each other. These are value objects that contain the "mask" and "offset" information into the BitVector32 object in a particular instance. As such, they belong to an instance. Think of the BitVector32 a the database table, and the sections as the columns, I imagine an analogous set of object could be IRow and IColumn -- these too would be instance, not static. It would only be the database itself that would be static. – Stacy Dudovitz Nov 24 '18 at 19:55
  • I would agree with you if you had initialized them via `info.CreateSection(1);` but is declared by `BitVector32.CreateSection(1);` which has no reference to your instanced BitVetcor32. Looking [at the source](https://referencesource.microsoft.com/#System/compmod/system/collections/specialized/bitvector32.cs,08050ee4d2585ff8) there is no reference to the vector stored in it, only the offsets. I am mostly sure that you could make those fields static and decrease the size of your struct by 4 bytes per flag (which for 32 flags can add up) – Scott Chamberlain Nov 24 '18 at 21:13
  • 1
    Don't think of BitVector as a database table, think of it as a "model" with a "view" applied to it. The sections are just different views applied to the same type of model, the view does not care which specific model you pass it, it just applies the view filter on whatever instance you give it. Your answer is the best one here IMHO, but it just needs to loose the bad advice about making the filters part of the struct instance. – Scott Chamberlain Nov 24 '18 at 21:18
  • 1
    OK, so I think we need some clarification... 1. The API for CreateSection is declared as: public static BitVector32.Section CreateSection(short maxValue) So it makes no difference whether I call it as info.CreateSection(...) or as BitVector32.Create(...), the end result is the same, it creates a Section struct object. 2. The object it creates is an instance object, and as such must be instanced... you would not want it to be static. Lifetime is important here. 3. Instead of a database metaphor, perhaps, schema might have been more appropriate. – Stacy Dudovitz Nov 24 '18 at 23:27
  • What is important to keep in mind is that BitVector32 allocates two things... 1. an array of bits 2. One or more section object that contain an offset into that array, and a bit mask that can be used to manipulate the bits within those bits . – Stacy Dudovitz Nov 24 '18 at 23:29
  • Yes, I agree with everything you said. What I don't agree is you need separate copies of the offsets for every instance of MyUnion. All instances of MyUnion can share static copies of the BitVector32.Sectoions, the way you have posted currently adds 300% overhead on the object size for the 3 flag version of the union and adds 3200% overhead on the object size for athe 32 flag version. If you made those sections static variables in the struct it would be 0% overhead. – Scott Chamberlain Nov 24 '18 at 23:44
  • Try [this version of your code example](https://gist.github.com/leftler/c03b32222442b6d211450a7be6b5ac14), works fine and sizeOf(MyUnion) is much smaller – Scott Chamberlain Nov 24 '18 at 23:55
  • You are correct, I just tried my own version. I should have clued into my own comment on schema... the *schema* never changes, so why shouldn't *that* be static? I just tried my own version, it works fine. Will be changing my post to reflect the newfound "d'oh" moment. This is what I love about the StackOverflow peer review process. Thank you : ) – Stacy Dudovitz Nov 25 '18 at 00:15
2

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

Alexei Shcherbakov
  • 1,125
  • 13
  • 10
  • This doesn't answer the problem of being able to store a value in say one of the flags, and then see the value in the info field. How would you see a "union" of both fields simultaneously? – Stacy Dudovitz Nov 24 '18 at 20:13
  • I still think the point is missed... if you have to make a second assignment, then you have two separate memory locations that do not occupy the same physical address space. What we want are two separate variables (info and the flags struct) that occupy the same physical memory address space. We should only have to do *one* assignment. We can then inspect either variable and see the results. This answer does not solve that requirement. – Stacy Dudovitz Nov 24 '18 at 23:32
  • No, Enum in C# is value type like uint. Enum actually is presented in memory like a number. When you use assignment it overwrites value, not reference – Alexei Shcherbakov Nov 24 '18 at 23:46