Search code examples
c#marshalling

Chained Marshal OffsetOf for Nested Struct


Have the same confusion as commented in this answer.

Quotes from CleanCoder:

Can Marshal.OffsetIf be used for fields/props in structs of structs? How this needs to be named? lets say for: Struct1.Struct2.Prop1 Whats the Identifier for Prop1 in Strct1? I want to get the unmanaged offset of a member, which is a child struct's member inside a parent struct. To be clear: a nested member's offset relative to the topmost struct.

I tried the offset2 method (see code below) and it can give me the correct offset, but I wonder if there is a better way?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

struct Address
{
    public int Building;
    public int Room;
}

struct Person
{
    public int Age;
    public int Height;
    public Address Addr;
}

class OffsetTest
{
    public static void CalOffset()
    {
        // OK
        int offset1 = (int)Marshal.OffsetOf(typeof(Person), nameof(Person.Addr));

        // Ok, manual add offsets (recursively if nested layer is deeper)
        int offset2 = (int)Marshal.OffsetOf(typeof(Person), nameof(Person.Addr)) + (int)Marshal.OffsetOf(typeof(Address), nameof(Address.Room));

        // Exception as expected: System.ArgumentException:“Field passed in is not a marshaled member of the type 'Person'. Arg_ParamName_Name”
        int offset3 = (int)Marshal.OffsetOf(typeof(Person), nameof(Person.Addr.Room));

        // Exception, too
        int offset4 = (int)Marshal.OffsetOf(typeof(Person), "Addr.Room");
    }
}

In C++, I'll consider create an object and calculate the offset by subtracting addresses directly, but in C# I don't know how to do it.


Solution

  • Can Marshal.OffsetOf be used for fields/props in structs of structs? How this needs to be named?

    No. It accepts field name which should be present in the provided type, not a path. From Marshal.OffsetOf docs:

    Parameters

    fieldName String

    The name of the field in the T type.

    So you will need to calculate the offset manually (as you do in " manual add offsets (recursively if nested layer is deeper)"). If needed you can wrap it into some convenient (extension) method which will accept "path" and use some cached reflection to determine the offset.

    P.S.

    Note that nameof(Person.Addr.Room) is "Room".