0

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

1 Answer 1

1
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 
Sign up to request clarification or add additional context in comments.

7 Comments

wow, ill try that out. I didn't even notice that
I've tried that, this is what the ILCode looks like: il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldsfld, field); il.Emit(OpCodes.Stobj, typeof(T)); il.Emit(OpCodes.Pop); il.Emit(OpCodes.Ldsfld, prop); il.Emit(OpCodes.Ldc_I4_1); il.Emit(OpCodes.Add); il.Emit(OpCodes.Stsfld, prop); il.Emit(OpCodes.Ret); I recieved a System.Reflection.TargetInvocationException with the following Message: InvalidProgramException: Common Language Runtime detected an invalid program.
Not sure why you are doing il.Emit(OpCodes.Pop) after stobj. As far as I can tell, the stack would be empty there. Your method was previously working when you just did ret right after the stobj. Given that the method returns void, the stack had to be empty or it would have failed at runtime.
the code you added is identical to the one I shared above.
I see ill try removing the Pop
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.