Skip to content

Commit

Permalink
Merge pull request #633 from Eddio0141/master
Browse files Browse the repository at this point in the history
MethodDelegate: allow for swapping value type with object, and allow ref for arg 0 on value type
  • Loading branch information
pardeike authored Dec 18, 2024
2 parents cd43272 + 3ba279f commit 899a3cd
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 3 deletions.
29 changes: 26 additions & 3 deletions Harmony/Tools/AccessTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1524,6 +1524,13 @@ public static FieldRef<F> StaticFieldRefAccess<F>(FieldInfo fieldInfo)
/// else, invocation of the delegate calls the exact specified <paramref name="method"/> (this is useful for calling base class methods)
/// Note: if <c>false</c> and <paramref name="method"/> is an interface method, an ArgumentException is thrown.
/// </param>
/// <param name="delegateArgs">
/// Only applies for instance methods, and if argument <paramref name="instance"/> is null.
/// This argument only matters if the target <paramref name="method"/> signature contains a value type (such as struct or primitive types),
/// and your <typeparamref name="DelegateType"/> argument is replaced by a non-value type
/// (usually <c>object</c>) instead of using said value type.
/// Use this if the generic arguments of <typeparamref name="DelegateType"/> doesn't represent the delegate's
/// arguments, and calling this function fails
/// <returns>A delegate of given <typeparamref name="DelegateType"/> to given <paramref name="method"/></returns>
/// <remarks>
/// <para>
Expand All @@ -1536,7 +1543,7 @@ public static FieldRef<F> StaticFieldRefAccess<F>(FieldInfo fieldInfo)
/// </para>
/// </remarks>
///
public static DelegateType MethodDelegate<DelegateType>(MethodInfo method, object instance = null, bool virtualCall = true) where DelegateType : Delegate
public static DelegateType MethodDelegate<DelegateType>(MethodInfo method, object instance = null, bool virtualCall = true, Type[] delegateArgs = null) where DelegateType : Delegate

Check warning on line 1546 in Harmony/Tools/AccessTools.cs

View workflow job for this annotation

GitHub Actions / Build Binaries (ubuntu, ubuntu-latest, ReleaseRef) / Upload Test Build Output Cache

XML comment has badly formed XML -- 'Expected an end tag for element 'param'.'

Check warning on line 1546 in Harmony/Tools/AccessTools.cs

View workflow job for this annotation

GitHub Actions / Build Binaries (ubuntu, ubuntu-latest, ReleaseRef) / Upload Test Build Output Cache

XML comment has badly formed XML -- 'Expected an end tag for element 'param'.'
{
if (method is null)
throw new ArgumentNullException(nameof(method));
Expand Down Expand Up @@ -1612,20 +1619,36 @@ public static DelegateType MethodDelegate<DelegateType>(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);
Expand Down
44 changes: 44 additions & 0 deletions HarmonyTests/Tools/TestAccessTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,48 @@ public void Test_AccessTools_MethodDelegate_OpenInstanceDelegates_InterfaceMetho
_ = Assert.Throws(typeof(ArgumentException), () => AccessTools.MethodDelegate<OpenMethodDel<Struct>>(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<OpenMethodDelRefInstance<Struct>>(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<OpenMethodDelRefInstance<Struct>>(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<OpenMethodDelRefInstance<Struct>>(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<OpenMethodDelBoxedArg<IInterface>>(interfaceTest, virtualCall: true, delegateArgs: delegateArgs_IInterface)(baseInstance, 456, ref f));
_ = Assert.Throws(typeof(ArgumentException), () => AccessTools.MethodDelegate<OpenMethodDelBoxedArg<IInterface>>(interfaceTest, virtualCall: false, delegateArgs: delegateArgs_IInterface)(baseInstance, 456, ref f));
// Assert.AreEqual("derived test 456 791 1", AccessTools.MethodDelegate<OpenMethodDelBoxedArg<IInterface>>(interfaceTest, virtualCall: true, delegateArgs: delegateArgs_IInterface)(derivedInstance, 456, ref f));
// _ = Assert.Throws(typeof(ArgumentException), () => AccessTools.MethodDelegate<OpenMethodDelBoxedArg<IInterface>>(interfaceTest, virtualCall: false, delegateArgs: delegateArgs_IInterface)(derivedInstance, 456, ref f));
// Assert.AreEqual("struct result 456 792 1", AccessTools.MethodDelegate<OpenMethodDelBoxedArg<IInterface>>(interfaceTest, virtualCall: true, delegateArgs: delegateArgs_IInterface)(structInstance, 456, ref f));
_ = Assert.Throws(typeof(ArgumentException), () => AccessTools.MethodDelegate<OpenMethodDelBoxedArg<IInterface>>(interfaceTest, virtualCall: false, delegateArgs: delegateArgs_IInterface)(structInstance, 456, ref f));
// Assert.AreEqual("base test 456 793 2", AccessTools.MethodDelegate<OpenMethodDelBoxedArg<Base>>(interfaceTest, virtualCall: true, delegateArgs: delegateArgs_Base)(baseInstance, 456, ref f));
_ = Assert.Throws(typeof(ArgumentException), () => AccessTools.MethodDelegate<OpenMethodDelBoxedArg<Base>>(interfaceTest, virtualCall: false, delegateArgs: delegateArgs_Base)(baseInstance, 456, ref f));
// Assert.AreEqual("derived test 456 794 2", AccessTools.MethodDelegate<OpenMethodDelBoxedArg<Base>>(interfaceTest, virtualCall: true, delegateArgs: delegateArgs_Base)(derivedInstance, 456, ref f));
_ = Assert.Throws(typeof(ArgumentException), () => AccessTools.MethodDelegate<OpenMethodDelBoxedArg<Base>>(interfaceTest, virtualCall: false, delegateArgs: delegateArgs_Base)(derivedInstance, 456, ref f));
// AccessTools.MethodDelegate<OpenMethodDelBoxedArg<Derived>>(interfaceTest, virtualCall: true)(baseInstance, 456, ref f)); // expected compile error
// AccessTools.MethodDelegate<OpenMethodDelBoxedArg<Derived>>(interfaceTest, virtualCall: false)(baseInstance, 456, ref f)); // expected compile error
// Assert.AreEqual("derived test 456 795 3", AccessTools.MethodDelegate<OpenMethodDelBoxedArg<Derived>>(interfaceTest, virtualCall: true, delegateArgs: delegateArgs_Derived)(derivedInstance, 456, ref f));
_ = Assert.Throws(typeof(ArgumentException), () => AccessTools.MethodDelegate<OpenMethodDelBoxedArg<Derived>>(interfaceTest, virtualCall: false, delegateArgs: delegateArgs_Derived)(derivedInstance, 456, ref f));
// Assert.AreEqual("struct result 456 796 1", AccessTools.MethodDelegate<OpenMethodDelBoxedArg<Struct>>(interfaceTest, virtualCall: true, delegateArgs: delegateArgs_Struct)(structInstance, 456, ref f));
_ = Assert.Throws(typeof(ArgumentException), () => AccessTools.MethodDelegate<OpenMethodDelBoxedArg<Struct>>(interfaceTest, virtualCall: false, delegateArgs: delegateArgs_Struct)(structInstance, 456, ref f));
}

[Test]
public void Test_AccessTools_MethodDelegate_StaticDelegates_InterfaceMethod()
{
Expand All @@ -520,6 +562,8 @@ public void Test_AccessTools_MethodDelegate_InvalidDelegates()

delegate string MethodDel(int n, ref float f);
delegate string OpenMethodDel<T>(T instance, int n, ref float f);
delegate string OpenMethodDelRefInstance<T>(ref T instance, int n, ref float f);
delegate string OpenMethodDelBoxedArg<T>(T instance, object n, ref float f);

[Test]
public void Test_AccessTools_HarmonyDelegate()
Expand Down

0 comments on commit 899a3cd

Please sign in to comment.