Home > Software engineering >  Marshal.OffsetOf don't reflect the runtime reality?
Marshal.OffsetOf don't reflect the runtime reality?

Time:03-23

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 ?

CodePudding user response:

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

CodePudding user response:

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.

  • Related