2

I would like to get the offset of a field in an unmanaged structure. For this I use the Marshal.OffsetOf method and I realized that the result does not reflect the Packing used with StructLayout

Take this example:

using System;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential, Pack = 4)]
unsafe public struct NETGROUP
{
    public bool Advise;
    public bool Active;
    public int UpdateRate;
    public double DeadBand;
}

namespace MyApp // Note: actual namespace depends on the project name.
{
    internal class Program
    {
        static void Main(string[] args)
        {
            unsafe
            {
                // Technique 1 (not correct)
                var oA = Marshal.OffsetOf(typeof(NETGROUP), "Advise").ToInt32();//0
                var oB = Marshal.OffsetOf(typeof(NETGROUP), "Active").ToInt32();//4
                var oC = Marshal.OffsetOf(typeof(NETGROUP), "UpdateRate").ToInt32();//8
                var oD = Marshal.OffsetOf(typeof(NETGROUP), "DeadBand").ToInt32();//12

                // Technique 2 (correct)
                NETGROUP ex = new NETGROUP();
                byte* addr = (byte*)&ex;

                var oAa = (byte*)(&ex.Advise) - addr;//0
                var oBb = (byte*)(&ex.Active) - addr;//1
                var oCc = (byte*)(&ex.UpdateRate) - addr;//4
                var oDd = (byte*)(&ex.DeadBand) - addr;//8
            }
        }
    }
}

I need to retrieve this offset in a generic constructor but the second technique (which is the correct one) does not allow me to achieve it without specifying the type explicitly

public CMember(Type type, string pszName, CType pType, uint uMod = Modifier.TMOD_NON, int nDim = 0) : base(DefineConstants.TOKN_MBR)
{
    m_sName = pszName;
    m_nOffset = Marshal.OffsetOf(type, pszName).ToInt32(); // !!!
    m_pType = pType;
    m_uMod = uMod;
    m_nDim = nDim;
}

Do you have an idea ?

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • Looks like `Marshal.OffsetOf` is correct and pointer calculations are wrong. In other words: the managed version of your `struct` is not honoring the `Pack` value – Charlieface Mar 22 '22 at 21:08
  • @Charlieface it is honoring the `Pack` value, but as I wrote in my answer - default unmanaged size of `bool` is 4 so `Pack` is honored by have no effect. – Guru Stron Mar 23 '22 at 16:57
  • Your pointer calculations are for the managed version, not the unmanaged version. `OffsetOf` does calculations only for the unmanaged version. The managed version does not have to honor the layout you specified, only the unmanaged version will. – Charlieface Mar 24 '22 at 05:13

2 Answers2

1

The OffsetOf only returns the layout of the unmanaged instances of a struct

OffsetOf provides the offset in terms of the unmanaged structure layout, which does not necessarily correspond to the offset of the managed structure layout. Marshaling the structure can transform the layout and alter the offset.

See also StructLayout

The common language runtime controls the physical layout of the data fields of a class or structure in managed memory. However, if you want to pass the type to unmanaged code, you can use the StructLayoutAttribute attribute to control the unmanaged layout of the type.

'ex' is a managed instance so you get the default layout

pm100
  • 48,078
  • 23
  • 82
  • 145
1

Marshal.OffsetOf is working as expected, the "issue" is the System.Boolean which is a non-blittable type and has unmanaged size of 4:

Console.WriteLine(sizeof(bool)); // 1
Console.WriteLine(Marshal.SizeOf(typeof(bool)));// prints 4

From UnmanagedType enum docs:

Bool A 4-byte Boolean value (true != 0, false = 0). This is the Win32 BOOL type

Changing struct to contain byte fields instead of bool ones produces the expected output:

[StructLayout(LayoutKind.Sequential, Pack = 4)]
unsafe public struct NETGROUP
{
    public byte Advise;
    public byte Active;
    ...
}

Console.WriteLine(Marshal.OffsetOf(typeof(NETGROUP), "Advise")); // 0
Console.WriteLine(Marshal.OffsetOf(typeof(NETGROUP), "Active")); // 1

Another approach is to marshal bool as UnmanagedType.I1:

A 1-byte signed integer. You can use this member to transform a Boolean value into a 1-byte, C-style bool (true = 1, false = 0).

[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct NETGROUP
{
    [MarshalAs(UnmanagedType.I1)]
    public byte Advise;
    [MarshalAs(UnmanagedType.I1)]
    public byte Active;
    ...
}

Some more info here.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132