1

I'm investigating on IPC data exchange with non-.Net applications based on Memory Mapped Files. Therefore I'm trying to understand on how C# can organize physical data layouts for that. So I started with unsafe structs as from what I read before this seemed to be the right technology. When playing with different data layouts for bool type values I got confused about this code and the results:

using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace UnsafeBoolArrayStruct
{
    [StructLayout(LayoutKind.Sequential, Pack = 2)]
    public unsafe struct StructOfBool
    {
        public bool Bool_00;
        public fixed bool Bool_01[1];
        public bool Bool_02;
        public fixed bool Bool_03[2];
        public fixed bool Bool_05[3];
        public fixed bool Bool_08[4];
        public fixed bool Bool_12[5];
        public fixed bool Bool_17[7];
        public fixed bool Bool_24[8];
        public fixed bool Bool_32[9];
        public fixed bool Bool_41[2];
        public bool Bool_43;
    }

    class Program
    {
        static void Main(string[] args)
        {
            StructOfBool dummy = new StructOfBool();

            int _Size = Marshal.SizeOf(typeof(StructOfBool));
            int _Pack = typeof(StructOfBool).StructLayoutAttribute.Pack;
            Console.WriteLine("{0} -> Pack: {1}, Size: {2}", typeof(StructOfBool).Name, _Pack, _Size);
            foreach (FieldInfo fi in typeof(StructOfBool).GetFields())
            {
                Type _Type;
                int _ArrayLength = 0;
                _Size = Marshal.SizeOf(fi.FieldType);
                var _Offset = Marshal.OffsetOf(typeof(StructOfBool), fi.Name);

                // Check for arrays
                var _Attribute = fi.GetCustomAttributes(typeof(FixedBufferAttribute), false);
                if (_Attribute.Length > 0)
                {   // Array
                    _Type = ((FixedBufferAttribute)_Attribute[0]).ElementType;
                    _ArrayLength = ((FixedBufferAttribute)_Attribute[0]).Length;
                }
                else
                    // Singular field
                    _Type = fi.FieldType;

                // Process found data
                Console.WriteLine("{0} -> Type: {1}, ArrayLength: {2}, Offset: {3}, Size: {4}", fi.Name, Type.GetTypeCode(_Type), _ArrayLength, _Offset, _Size);
            }
            Console.ReadKey();
        }
    }
}

The result shows like this:

StructOfBool -> Pack: 2, Size: 64

Bool_00 -> Type: Boolean, ArrayLength: 0, Offset: 0, Size: 4

Bool_01 -> Type: Boolean, ArrayLength: 1, Offset: 4, Size: 4

Bool_02 -> Type: Boolean, ArrayLength: 0, Offset: 8, Size: 4

Bool_03 -> Type: Boolean, ArrayLength: 2, Offset: 12, Size: 4

Bool_05 -> Type: Boolean, ArrayLength: 3, Offset: 16, Size: 4

Bool_08 -> Type: Boolean, ArrayLength: 4, Offset: 20, Size: 4

Bool_12 -> Type: Boolean, ArrayLength: 5, Offset: 24, Size: 5

Bool_17 -> Type: Boolean, ArrayLength: 7, Offset: 30, Size: 7

Bool_24 -> Type: Boolean, ArrayLength: 8, Offset: 38, Size: 8

Bool_32 -> Type: Boolean, ArrayLength: 9, Offset: 46, Size: 9

Bool_41 -> Type: Boolean, ArrayLength: 2, Offset: 56, Size: 4

Bool_43 -> Type: Boolean, ArrayLength: 0, Offset: 60, Size: 4

It looks like that packing is handled differently at least on singular fields compared to arrays. Why does Bool00 with one bool value take 4 bytes, Bool03 with two bool values also take 4 bytes, but Bool12 with five values just 5 bytes??

Does anybody know, why?

Johan
  • 8,068
  • 1
  • 33
  • 46
Uli-VS
  • 31
  • 4
  • Which architecture do you use? Most likely this is an alignment issue. – weismat Nov 09 '15 at 13:52
  • I think there is a pointer to real data location. The pointer may be different size address pointer to actual data. The structure contains a type as first location which will indicate the variable type and if the structure contains the actual data or a pointer to the data. Also notice the offsets is the size rounded up to even number (aligned). – jdweng Nov 09 '15 at 14:01
  • 2
    You could not possibly pick a worse interop type, the *bool* type has very poor standardization. It is 1 byte in C++, 4 bytes in C, 2 bytes in COM, 1 byte in the CLR. Making a fixed buffer out of it suddenly changes that, now it interops as a byte. Using Pack = 2 is also very unlikely, nobody uses that. Get ahead by using byte instead of bool. And use a normal byte[] with [MarshalAs(UnmanagedType.ByValArray), SizeConst = xx]. You can add a property to convert the byte to bool. – Hans Passant Nov 09 '15 at 14:05

1 Answers1

0

but Bool12 with five values just 5 bytes??

To be fair, what your result shows is that Bool12 actually takes six bytes in the struct.

The Marshal.SizeOf() method doesn't tell you anything about the managed representation of the data, including the size of fields in a struct. That method is needed for code that needs to know what the unmanaged version of a given type will need in terms of space.

I don't know all the layout rules for managed types off the top of my head. But your observations can be explained by considering that the actual size of an array need not be a multiple of the minimum size of the element of the array, or even of the array itself. So you get bool and bool[] fields that are four bytes, because they can't be any smaller, but once you get beyond that size, the pack (alignment) value takes over, putting the fields at even-valued offsets.

The reported length of these fields you are obtaining by using Marshal.SizeOf() which, as I noted above, isn't related to the managed storage at all. It's telling you how many bytes a destination buffer in unmanaged code would need to be to store the information in that field.

Even if it were related (and by coincidence, in a way it is…just because the CLR side works very similarly to the unmanaged side), that doesn't change the fact that fields in your struct need to be aligned to two-byte boundaries, so the offsets for subsequent fields don't always match up with the reported length of their respective preceding field.


Related links:
How to check the number of bytes consumed by my structure?
sizeof vs Marshal.SizeOf
Computing the Size of a Structure
What’s the difference? sizeof and Marshal.SizeOf

Community
  • 1
  • 1
Peter Duniho
  • 68,759
  • 7
  • 102
  • 136