Walt D
Walt D

Reputation: 4731

How to get offset of field within unmanaged struct without using Marshal?

I have an unmanaged struct containing fields whose byte offsets within it I want to determine. However, some of these fields are value tuples, and because value tuples are generic (EDIT: This is actually because ValueTuple uses LayoutKind.Auto; see answer below), I can't use Marshal.OffsetOf to determine the offset of any of the fields within the struct. (Even though it's not "marshal-able", it IS unmanaged and blittable to unmanaged memory.) Here's a trivialized example showing what I'm trying to do:

using System;
using System.Runtime.InteropServices;

IntPtr offset = Marshal.OffsetOf<TestStruct>(nameof(TestStruct.B)); // Throws exception
Console.WriteLine(offset);

[StructLayout(LayoutKind.Sequential, Pack=4)]
struct TestStruct
{
    public int A;
    public (int, int) B;
}

The above code throws an exception when calling Marshal.OffsetOf:

Type 'TestStruct' cannot be marshaled as an unmanaged structure; no meaningful size or offset can be computed.

So how can I programmatically determine the offset of all of the fields within an unmanaged struct, but without using Marshal.OffsetOf? I'm looking for a solution that will work on an arbitrary (unmanaged) struct type supplied via either generic parameter or Type object.

(Obviously, the above example is trivially easy to compute by hand. This is not my real-world example. My real-world use case is a library that uses reflection to iterate over all fields within arbitrary structs defined by the client application, and thus it is not possible to compute the offsets by hand.)

Upvotes: 1

Views: 931

Answers (1)

Iridium
Iridium

Reputation: 23731

Using Marshal.OffsetOf<>() doesn't work because System.ValueTuple<,> has automatic layout, (this is also likely why the packing you specified in your example is ignored, resulting B having an offset of 8 on x64).

It's not clear why you need to use tuples specifically, rather than e.g. defining your own structs with defined layout and using these instead, e.g.:

[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct TestStruct
{
    public int A;
    public InnerStruct B;
}

[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct InnerStruct
{
    public int A;
    public int B;
}

With the above definitions, Marshal.OffsetOf<TestStruct>(nameof(TestStruct.B)) works fine, and returns 4 as expected.

If there is some specific reason that using tuples is necessary, then you could provide your own System.ValueTuple<...> implementations with layout, though it feels like a bit of a hack.

For example, if you place the following definition somewhere within the same project that defines TestStruct:

namespace System
{
    [StructLayout(LayoutKind.Sequential, Pack = 4)]
    public struct ValueTuple<T1, T2>
    {
        public T1 Item1;
        public T2 Item2;

        // Appropriate implementation to match the behaviour of the framework's ValueTuple here...
    }
}

Then the using the original definition of TestStruct from your question:

[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct TestStruct
{
    public int A;
    public (int, int) B;
}

Will use the locally defined System.ValueTuple<,> implementation. Since the whole struct now has layout, it can then be used with Marshal.OffsetOf<>(), which will again return 4 for the offset of TestStruct.B.

Upvotes: 1

Related Questions