2

In .Net Core how do you get the memory address (IntPtr) of a primitive field when the parent object is not known at compile time?

If the object class was a struct and blittable then I could use Marshel.Offset, but unfortunately it is not.

The below code illustrates what I am trying to do

    class Example   // this class is defined in a different assembly and is not known at compile time
    {
        public double foo;
    }

    static void Main(string[] args)
    {
        Example obj = new Example();
        obj.foo = 123;

        var hdl = GCHandle.Alloc(obj, GCHandleType.Pinned);
        IntPtr foo_add = GetAddressOf(obj, "foo");

        hdl.Free();
    }

    static IntPtr GetAddressOf(object pinned_object, string field_name)
    {
        FieldInfo field = pinned_object.GetType().GetField(field_name);
        return field.GetFieldAddress(pinned_object); // Unfortunatly this function does not exist, what are the alternatives?
    }

The alternative question looks at getting the memory address of an object, this can be done using GCHandle.Alloc and does not work for primitives.

user425678
  • 720
  • 7
  • 17
  • Clearly *not* even close to a duplicate. Op asked about address of a primitive field. Other question is address of an object. – JamesHoux Sep 21 '19 at 05:17

1 Answers1

3

The below answer describes how to obtain a managed referenced to a field using the "ref return" feature in C# 7.0:

https://stackoverflow.com/a/45046664/425678

Based on this brilliant answer we can modify the function create_refgetter to obtain an unmanaged pointer. This makes use of the little known function __makeref which is required when working with generic types (detailed explanation)

This function is then wrapped with the function GetAddressOf which allows calls without specifying the generic types.

    static IntPtr GetAddressOf(object pinned_object, string field_name)
    {
        var fi_val = pinned_object.GetType().GetField(field_name);
        var mi_all = typeof(Program).GetMethod("create_refgetter", BindingFlags.Static | BindingFlags.Public);

        var mi_generic = mi_all.MakeGenericMethod(pinned_object.GetType(), fi_val.FieldType);
        var ptr = (IntPtr) mi_generic.Invoke(null, new object[] {pinned_object, field_name });

        return ptr;
    }

    https://stackoverflow.com/a/45046664/425678

    public delegate ref U RefGetter<T, U>(T obj);
    public static IntPtr create_refgetter<T, U>(object obj,string s_field)
    {
        const BindingFlags bf = BindingFlags.NonPublic |
                                BindingFlags.Public |
                                BindingFlags.Instance |
                                BindingFlags.DeclaredOnly;

        var fi = typeof(T).GetField(s_field, bf);
        if (fi == null)
            throw new MissingFieldException(typeof(T).Name, s_field);

        var s_name = "__refget_" + typeof(T).Name + "_fi_" + fi.Name;

        // workaround for using ref-return with DynamicMethod:
        //   a.) initialize with dummy return value
        var dm = new DynamicMethod(s_name, typeof(U), new[] { typeof(T) }, typeof(T), true);

        //   b.) replace with desired 'ByRef' return value
        dm.GetType().GetField("m_returnType", bf).SetValue(dm, typeof(U).MakeByRefType());

        var il = dm.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldflda, fi);
        il.Emit(OpCodes.Ret);

        RefGetter<T, U> ref_getter = (RefGetter<T, U>)dm.CreateDelegate(typeof(RefGetter<T, U>));

        unsafe
        {
            TypedReference t_ref = __makeref(ref_getter((T)obj));

            IntPtr ptr = *((IntPtr*)&t_ref);
            return ptr;
        }
    }
}

Exmaple:

class Example   // this class is defined in a different assembly and is not known at compile time
{
    public double foo;
}

static void Main(string[] args)
{
    Example obj = new Example();
    obj.foo = 123;

    var hdl = GCHandle.Alloc(obj);

    unsafe
    {
        IntPtr foo_add = GetAddressOf(obj, "foo");

        double* ptr = (double*) foo_add;
        Console.WriteLine("Current Value: {0}", *ptr);

        *ptr = 4;
        Console.WriteLine("Updated Value: {0}", obj.foo);
    }

    hdl.Free();

    Console.ReadLine();
}

Example Output:

Current Value: 123
Updated Value: 4
user425678
  • 720
  • 7
  • 17