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
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