简体   繁体   中英

Setting the value of a static field using Reflection.Emit fails in Unity

I'm trying to set the value of a static field using Reflection.Emit (I don't have access to .NET 4's Expression.Assign because I'm stuck with Unity's .NET 3.5).

My current code is as follows:

public Action<TTarget, TField> GetSetter<TTarget, TField>(FieldInfo fieldInfo)
{
    DynamicMethod setterMethod = new DynamicMethod
    (
        "setter",
        typeof(void),
        new Type[] { typeof(TTarget), typeof(TField) },
        typeof(TTarget)
    );

    var setterIL = setterMethod.GetILGenerator();

    if (fieldInfo.IsStatic)
    {
        setterIL.Emit(OpCodes.Ldnull);
    }
    else
    {
        setterIL.Emit(OpCodes.Ldarg_0);
    }

    setterIL.Emit(OpCodes.Ldarg_1);
    setterIL.Emit(OpCodes.Stfld, fieldInfo);
    setterIL.Emit(OpCodes.Ret);

    return (Action<TTarget, TField>)setterMethod.CreateDelegate(typeof(Action<TTarget, TField>));
}

And then, I invoke the setter using:

public class Static
{
    public static int x;
}

var fieldInfo = typeof(Static).GetField("x");

var setter = GetSetter<Static, int>(fieldInfo);

setter.Invoke(null, 123);

I get this error message:

NullReferenceException: Object reference not set to an instance of an object (wrapper dynamic-method) setter (...,int)

I thought loading null as the first argument ( Ldnull opcode) would fix it, but it doesn't seem to work. What am I doing wrong?

Update: it seems the exception is only triggered when the code runs from within Unity (latest, 5.5.0p4). In a .NET 3.5 console application created from Visual Studio, there is no issue. Could there be an issue with Unity's Mono compiler?

Here is the full code to test from a Tools > Debug IL menu item in Unity.

using System;
using System.Reflection;
using System.Reflection.Emit;
using UnityEditor;

class Program
{
    public static Action<TTarget, TField> GetSetter<TTarget, TField>(FieldInfo fieldInfo)
    {
        DynamicMethod setterMethod = new DynamicMethod
        (
            "setter",
            typeof(void),
            new Type[] { typeof(TTarget), typeof(TField) },
            typeof(TTarget)
        );

        var setterIL = setterMethod.GetILGenerator();

        setterIL.Emit(OpCodes.Ldarg_0);
        setterIL.Emit(OpCodes.Ldarg_1);
        setterIL.Emit(OpCodes.Stfld, fieldInfo);
        setterIL.Emit(OpCodes.Ret);

        return (Action<TTarget, TField>)setterMethod.CreateDelegate(typeof(Action<TTarget, TField>));
    }

    public class Static
    {
        public static int x;
    }

    [MenuItem("Tools/Debug IL")]
    static void Debug()
    {
        var fieldInfo = typeof(Static).GetField("x");

        var setter = GetSetter<Static, int>(fieldInfo);

        setter.Invoke(null, 123);

        Debug.Log("Static field assignment succeeded.");
    }
}

Got it working with OpCodes.Stsfld (set static field) instead:

if (fieldInfo.IsStatic)
{
    setterIL.Emit(OpCodes.Ldarg_0);
    setterIL.Emit(OpCodes.Stsfld, fieldInfo);
    setterIL.Emit(OpCodes.Ret);
}
else
{
    setterIL.Emit(OpCodes.Ldarg_0);
    setterIL.Emit(OpCodes.Ldarg_1);
    setterIL.Emit(OpCodes.Stfld, fieldInfo);
    setterIL.Emit(OpCodes.Ret);
}

The .NET runtime is probably more lenient than the Mono runtime behind the scenes (ie: it allows Stfld even for a static field, just ignoring the first parameter, whereas Mono doesn't), which would explain why the issue only occurred in Unity.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM