Search code examples
c#opcodedynamicmethodilgenerator

Create a Field using FieldBuilder and add one to Field Value in MethodBuilder ILCode Emit


I have a Method that creates a MethodBuilder Method and defines the Behaviour using ILGenerator and Emit + OpCodes.

This Method was created with the help of a previous StackOverflow Question I made - Thanks to @Marc Gravell for the help

The Behaviour is very specific and there is a reason I must create a method using MethodBuilder rather than standard method definition in C#, The Method is planned to be used for a Harmony PostFix HarmonyMethod Definition to Patch the Logic at runtime, and I don't want to use a static Method since ill be doing a lot of Method Mocks and I don't want to have a lot of static classes and methods, So I Created the following Class:

public class PostFixPatchFactory<T>
{
    public T Value { get; set; }
    public Exception Exception { get; set; }
    public PostFixPatchFactory(T value)
        => Value = value;
    public int TimesTriggered;
    public FieldInfo field;
    public MethodInfo GeneratePostfix()
    {
        string fieldName = $"DynamicField_{Guid.NewGuid()}";

        AssemblyBuilder assembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName($"DynamicAssembly_{Guid.NewGuid()}"), AssemblyBuilderAccess.RunAndCollect);
        ModuleBuilder module = assembly.DefineDynamicModule($"DynamicModule_{Guid.NewGuid()}");
        TypeBuilder typeBuilder = module.DefineType($"DynamicType_{Guid.NewGuid()}", TypeAttributes.Public);
        FieldBuilder field = typeBuilder.DefineField(fieldName, typeof(T), FieldAttributes.Private | FieldAttributes.Static);
        
        if (Value is Exception)
        {
            typeBuilder.DefineMethod("PostFix", MethodAttributes.Static | MethodAttributes.Public, typeof(void), null)
                .GetILGenerator().ThrowException(typeof(T));
        }
        else
        {
            MethodBuilder method = typeBuilder.DefineMethod("PostFix", MethodAttributes.Static | MethodAttributes.Public,
                typeof(void), new Type[] { typeof(T).MakeByRefType() });

            var il = method.GetILGenerator();            

            method.DefineParameter(1, ParameterAttributes.None, "__result");

            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldsfld, field);
            il.Emit(OpCodes.Stobj, typeof(T));
            il.Emit(OpCodes.Ret);
        }
        var type = typeBuilder.CreateType();
        type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Static).SetValue(null, Value);
        return type.GetMethod("PostFix", 
BindingFlags.Public | BindingFlags.Static);
    }
    public bool Prefix()
    {
        TimesTriggered++;
        return false;
    }
}

This Class is used as follows:

var harmony = new Harmony(nameof(Program));

        HarmonyMethod prefix = new HarmonyMethod(typeof(Program).GetMethod(nameof(Prefix)));

        var postFixPatchFactory = new PostFixPatchFactory<long>(6);
        
        MethodInfo originalMethod = typeof(Program).GetMethod(nameof(TestMethodToChange));

        MethodInfo scenarioToMockExample2 = typeof(ScenariosToMock).GetMethod("Example", BindingFlags.Static | BindingFlags.NonPublic);

        HarmonyMethod scenarioToMockExample2PostFix = new HarmonyMethod(g.GeneratePostfix());

        harmony.Patch(scenarioToMockExample2, prefix, scenarioToMockExample2PostFix);

        var result = TestMethodToChange();

        Console.WriteLine($"list result:[{result.Join(null,",")}]");

This works very nicely - except its missing one feature that I want - which is to have a property on the PostFixPatchFactory called TimesExcecuted or something of that variety so that I can track how many times a Method I patched got excecuted - I am struggling to Implement this feature - I want this feature to be part of the IL Emit Code within the new method definition - and I want the method to be accessible as an Instance public field or Property so that it can be used as follows:

int timesExcecuted = postFixPatchFactory.TimesExcecuted;
Console.WriteLine($"Method has been Triggered: {timesExcecuted}");

Please Help me - i'm not very good at IL or Opcodes so any help would be greatly Appreciated

I have tried using something along the lines of a new FieldBuilder:

FieldBuilder prop = typeBuilder.DefineField($"DynamicProperty_{Guid.NewGuid()}", typeof(int), 
            FieldAttributes.Public | FieldAttributes.Static);

And use something along the lines of the following ILCode:

            il.Emit(OpCodes.Ldsfld, prop);
            il.Emit(OpCodes.Ldc_I4_1);
            il.Emit(OpCodes.Add);

But this did not work and returned a Invalid Code Exception


Solution

  • il.Emit(OpCodes.Ldsfld, prop);
    il.Emit(OpCodes.Ldc_I4_1);
    il.Emit(OpCodes.Add);
    

    You're leaving "prop + 1" on the stack. You need to save it back to the field

    il.Emit(OpCodes.Stsfld, prop);
    

    I tried the following code in .net 6

    AssemblyBuilder assembly =
             AssemblyBuilder.DefineDynamicAssembly(new AssemblyName($"DynamicAssembly_{Guid.NewGuid()}"),
                AssemblyBuilderAccess.RunAndCollect);
          ModuleBuilder module = assembly.DefineDynamicModule($"DynamicModule_{Guid.NewGuid()}");
          TypeBuilder typeBuilder = module.DefineType($"DynamicType_{Guid.NewGuid()}", TypeAttributes.Public);
          FieldBuilder field =
             typeBuilder.DefineField(fieldName, typeof(T), FieldAttributes.Private | FieldAttributes.Static);
    
          FieldBuilder prop = typeBuilder.DefineField($"DynamicProperty_{Guid.NewGuid()}", typeof(int),
             FieldAttributes.Public | FieldAttributes.Static);
    
          MethodBuilder method = typeBuilder.DefineMethod("PostFix", MethodAttributes.Static | MethodAttributes.Public,
             typeof(void), new Type[] { typeof(T).MakeByRefType() });
    
          var il = method.GetILGenerator();
    
          method.DefineParameter(1, ParameterAttributes.None, "__result");
    
    
          il.Emit(OpCodes.Ldarg_0);
       il.Emit(OpCodes.Ldsfld, field);
       il.Emit(OpCodes.Stobj, typeof(T));
       il.Emit(OpCodes.Ldsfld, prop);
       il.Emit(OpCodes.Ldc_I4_1);
       il.Emit(OpCodes.Add);
       il.Emit(OpCodes.Stsfld, prop);
       il.Emit(OpCodes.Ret);
    
    
       var t = typeBuilder.CreateType();
          var ot = Activator.CreateInstance(t);
          var m = t.GetMethod("PostFix");
          
          
          for (var i = 0; i < 5; i++)
             m.Invoke(null, new Object[] { value });
    
    
          var propVal = t.GetField(prop.Name).GetValue(null);
    
       Debug.WriteLine($"propVal is {propVal}");
    

    The output was

    propVal is 5