diff --git a/Harmony/Tools/AccessTools.cs b/Harmony/Tools/AccessTools.cs index a6656b50..303b672e 100644 --- a/Harmony/Tools/AccessTools.cs +++ b/Harmony/Tools/AccessTools.cs @@ -1524,6 +1524,13 @@ public static FieldRef StaticFieldRefAccess(FieldInfo fieldInfo) /// else, invocation of the delegate calls the exact specified (this is useful for calling base class methods) /// Note: if false and is an interface method, an ArgumentException is thrown. /// + /// + /// Only applies for instance methods, and if argument is null. + /// This argument only matters if the target signature contains a value type (such as struct or primitive types), + /// and your argument is replaced by a non-value type + /// (usually object) instead of using said value type. + /// Use this if the generic arguments of doesn't represent the delegate's + /// arguments, and calling this function fails /// A delegate of given to given /// /// @@ -1536,7 +1543,7 @@ public static FieldRef StaticFieldRefAccess(FieldInfo fieldInfo) /// /// /// - public static DelegateType MethodDelegate(MethodInfo method, object instance = null, bool virtualCall = true) where DelegateType : Delegate + public static DelegateType MethodDelegate(MethodInfo method, object instance = null, bool virtualCall = true, Type[] delegateArgs = null) where DelegateType : Delegate { if (method is null) throw new ArgumentNullException(nameof(method)); @@ -1612,20 +1619,36 @@ public static DelegateType MethodDelegate(MethodInfo method, objec parameterTypes[0] = declaringType; for (var i = 0; i < numParameters; i++) parameterTypes[i + 1] = parameters[i].ParameterType; + var delegateArgsResolved = delegateArgs ?? delegateType.GetGenericArguments(); + var dynMethodReturn = delegateArgsResolved.Length < parameterTypes.Length + ? parameterTypes + : delegateArgsResolved; var dmd = new DynamicMethodDefinition( "OpenInstanceDelegate_" + method.Name, method.ReturnType, - parameterTypes) + dynMethodReturn) { // OwnerType = declaringType }; var ilGen = dmd.GetILGenerator(); - if (declaringType != null && declaringType.IsValueType) + if (declaringType != null && declaringType.IsValueType && delegateArgsResolved.Length > 0 && + !delegateArgsResolved[0].IsByRef) + { ilGen.Emit(OpCodes.Ldarga_S, 0); + } else ilGen.Emit(OpCodes.Ldarg_0); for (var i = 1; i < parameterTypes.Length; i++) + { ilGen.Emit(OpCodes.Ldarg, i); + // unbox to make il code valid + if (parameterTypes[i].IsValueType && i < delegateArgsResolved.Length && + !delegateArgsResolved[i].IsValueType) + { + ilGen.Emit(OpCodes.Unbox_Any, parameterTypes[i]); + } + } + ilGen.Emit(OpCodes.Call, method); ilGen.Emit(OpCodes.Ret); return (DelegateType)dmd.Generate().CreateDelegate(delegateType); diff --git a/HarmonyTests/Tools/TestAccessTools.cs b/HarmonyTests/Tools/TestAccessTools.cs index 5437e72e..e8a95c46 100644 --- a/HarmonyTests/Tools/TestAccessTools.cs +++ b/HarmonyTests/Tools/TestAccessTools.cs @@ -500,6 +500,48 @@ public void Test_AccessTools_MethodDelegate_OpenInstanceDelegates_InterfaceMetho _ = Assert.Throws(typeof(ArgumentException), () => AccessTools.MethodDelegate>(interfaceTest, virtualCall: false)(structInstance, 456, ref f)); } + [Test] + public void Test_AccessTools_MethodDelegate_OpenInstanceDelegates_Arg0ByRef() + { + // only applies to struct, since you want to mutate structs via ref + var f = 789f; + + var structInstance = new Struct(); + // repeat for mutation + Assert.AreEqual("struct result 456 790 1", AccessTools.MethodDelegate>(structTest, virtualCall: false, delegateArgs: [typeof(Struct).MakeByRefType(), typeof(int), typeof(float).MakeByRefType()])(ref structInstance, 456, ref f)); + Assert.AreEqual("struct result 456 791 2", AccessTools.MethodDelegate>(structTest, virtualCall: false, delegateArgs: [typeof(Struct).MakeByRefType(), typeof(int), typeof(float).MakeByRefType()])(ref structInstance, 456, ref f)); + Assert.AreEqual("struct result 456 792 3", AccessTools.MethodDelegate>(structTest, virtualCall: false, delegateArgs: [typeof(Struct).MakeByRefType(), typeof(int), typeof(float).MakeByRefType()])(ref structInstance, 456, ref f)); + } + + [Test] + public void Test_AccessTools_MethodDelegate_OpenInstanceDelegates_BoxedArgs() + { + var f = 789f; + var baseInstance = new Base(); + var derivedInstance = new Derived(); + var structInstance = new Struct(); + var delegateArgs_IInterface = new Type[] { typeof(IInterface), typeof(object), typeof(float).MakeByRefType() }; + var delegateArgs_Base = new Type[] { typeof(Base), typeof(object), typeof(float).MakeByRefType() }; + var delegateArgs_Derived = new Type[] { typeof(Derived), typeof(object), typeof(float).MakeByRefType() }; + var delegateArgs_Struct = new Type[] { typeof(Struct), typeof(object), typeof(float).MakeByRefType() }; + // Assert.AreEqual("base test 456 790 1", AccessTools.MethodDelegate>(interfaceTest, virtualCall: true, delegateArgs: delegateArgs_IInterface)(baseInstance, 456, ref f)); + _ = Assert.Throws(typeof(ArgumentException), () => AccessTools.MethodDelegate>(interfaceTest, virtualCall: false, delegateArgs: delegateArgs_IInterface)(baseInstance, 456, ref f)); + // Assert.AreEqual("derived test 456 791 1", AccessTools.MethodDelegate>(interfaceTest, virtualCall: true, delegateArgs: delegateArgs_IInterface)(derivedInstance, 456, ref f)); + // _ = Assert.Throws(typeof(ArgumentException), () => AccessTools.MethodDelegate>(interfaceTest, virtualCall: false, delegateArgs: delegateArgs_IInterface)(derivedInstance, 456, ref f)); + // Assert.AreEqual("struct result 456 792 1", AccessTools.MethodDelegate>(interfaceTest, virtualCall: true, delegateArgs: delegateArgs_IInterface)(structInstance, 456, ref f)); + _ = Assert.Throws(typeof(ArgumentException), () => AccessTools.MethodDelegate>(interfaceTest, virtualCall: false, delegateArgs: delegateArgs_IInterface)(structInstance, 456, ref f)); + // Assert.AreEqual("base test 456 793 2", AccessTools.MethodDelegate>(interfaceTest, virtualCall: true, delegateArgs: delegateArgs_Base)(baseInstance, 456, ref f)); + _ = Assert.Throws(typeof(ArgumentException), () => AccessTools.MethodDelegate>(interfaceTest, virtualCall: false, delegateArgs: delegateArgs_Base)(baseInstance, 456, ref f)); + // Assert.AreEqual("derived test 456 794 2", AccessTools.MethodDelegate>(interfaceTest, virtualCall: true, delegateArgs: delegateArgs_Base)(derivedInstance, 456, ref f)); + _ = Assert.Throws(typeof(ArgumentException), () => AccessTools.MethodDelegate>(interfaceTest, virtualCall: false, delegateArgs: delegateArgs_Base)(derivedInstance, 456, ref f)); + // AccessTools.MethodDelegate>(interfaceTest, virtualCall: true)(baseInstance, 456, ref f)); // expected compile error + // AccessTools.MethodDelegate>(interfaceTest, virtualCall: false)(baseInstance, 456, ref f)); // expected compile error + // Assert.AreEqual("derived test 456 795 3", AccessTools.MethodDelegate>(interfaceTest, virtualCall: true, delegateArgs: delegateArgs_Derived)(derivedInstance, 456, ref f)); + _ = Assert.Throws(typeof(ArgumentException), () => AccessTools.MethodDelegate>(interfaceTest, virtualCall: false, delegateArgs: delegateArgs_Derived)(derivedInstance, 456, ref f)); + // Assert.AreEqual("struct result 456 796 1", AccessTools.MethodDelegate>(interfaceTest, virtualCall: true, delegateArgs: delegateArgs_Struct)(structInstance, 456, ref f)); + _ = Assert.Throws(typeof(ArgumentException), () => AccessTools.MethodDelegate>(interfaceTest, virtualCall: false, delegateArgs: delegateArgs_Struct)(structInstance, 456, ref f)); + } + [Test] public void Test_AccessTools_MethodDelegate_StaticDelegates_InterfaceMethod() { @@ -520,6 +562,8 @@ public void Test_AccessTools_MethodDelegate_InvalidDelegates() delegate string MethodDel(int n, ref float f); delegate string OpenMethodDel(T instance, int n, ref float f); + delegate string OpenMethodDelRefInstance(ref T instance, int n, ref float f); + delegate string OpenMethodDelBoxedArg(T instance, object n, ref float f); [Test] public void Test_AccessTools_HarmonyDelegate()