diff --git a/Harmony/Public/Patching/NativeDetourMethodPatcher.cs b/Harmony/Public/Patching/NativeDetourMethodPatcher.cs
index 8f27d60e..7ac04adf 100644
--- a/Harmony/Public/Patching/NativeDetourMethodPatcher.cs
+++ b/Harmony/Public/Patching/NativeDetourMethodPatcher.cs
@@ -29,21 +29,17 @@ public class NativeDetourMethodPatcher : MethodPatcher
private IDisposable _realReplacementPin;
private IDisposable _altHandle;
- private readonly MethodSignature _staticSignature;
-
+ private readonly MethodSignature _signature;
private readonly MethodInfo _altEntryMethod;
private readonly IDisposable _altEntryPin;
///
public NativeDetourMethodPatcher(MethodBase original) : base(original)
{
- var staticSignature = new MethodSignature(Original, true);
- _staticSignature = staticSignature;
-
- var altEntryProxyMethod = GenerateAltEntryProxyStub(Original, staticSignature);
- _altEntryMethod = altEntryProxyMethod;
- _altEntryPin = PlatformTriple.Current.PinMethodIfNeeded(altEntryProxyMethod);
- PlatformTriple.Current.Compile(altEntryProxyMethod);
+ _signature = new MethodSignature(Original);
+ _altEntryMethod = GenerateAltEntryProxyStub(Original, _signature);
+ _altEntryPin = PlatformTriple.Current.PinMethodIfNeeded(_altEntryMethod);
+ PlatformTriple.Current.Compile(_altEntryMethod);
}
private static MethodInfo GenerateAltEntryProxyStub(MethodBase original, MethodSignature signature)
@@ -122,12 +118,12 @@ public override DynamicMethodDefinition CopyOriginal() =>
private DynamicMethodDefinition GenerateAltEntryProxyCaller(string name)
{
- var dmd = _staticSignature.CreateDmd(name);
+ var dmd = _signature.CreateDmd(name);
var il = new ILContext(dmd.Definition);
var c = new ILCursor(il);
- for (var i = 0; i < _staticSignature.ParameterCount; i++)
+ for (var i = 0; i < _signature.ParameterCount; i++)
c.EmitLdarg(i);
c.EmitCall(_altEntryMethod);
c.EmitRet();
diff --git a/HarmonyTests/Patching/Assets/NativeDetourClasses.cs b/HarmonyTests/Patching/Assets/NativeDetourClasses.cs
index 81626682..3788dd59 100644
--- a/HarmonyTests/Patching/Assets/NativeDetourClasses.cs
+++ b/HarmonyTests/Patching/Assets/NativeDetourClasses.cs
@@ -5,7 +5,54 @@
namespace HarmonyTests.Patching.Assets;
-public class ExternalMethod_Patch
+public class ExternalInstanceMethod_StringIsInterned_Patch
+{
+ public static IEnumerable Transpiler(IEnumerable instructions) =>
+ instructions;
+
+ private const string UniqueString = nameof(ExternalInstanceMethod_StringIsInterned_Patch);
+
+ public const string PrefixInput = $"{UniqueString} {nameof(PrefixInput)}";
+ public const string PrefixOutput = $"{UniqueString} {nameof(PrefixOutput)}";
+ public static void Prefix(ref string __instance, ref string __result, ref bool __runOriginal)
+ {
+ if (__instance == PrefixInput)
+ {
+ __result = PrefixOutput;
+ __runOriginal = false;
+ }
+ }
+
+ public const string PostfixInput = $"{UniqueString} {nameof(PostfixInput)}";
+ public const string PostfixOutput = $"{UniqueString} {nameof(PostfixOutput)}";
+ public static void Postfix(ref string __instance, ref string __result)
+ {
+ if (__instance == PostfixInput)
+ {
+ __result = PostfixOutput;
+ }
+ }
+
+ public static readonly Type TranspiledException = typeof(UnauthorizedAccessException);
+ public static IEnumerable TranspilerThrow(IEnumerable instructions)
+ {
+ yield return new CodeInstruction(OpCodes.Newobj, TranspiledException.GetConstructor([]));
+ yield return new CodeInstruction(OpCodes.Throw);
+ }
+
+ public const string FinalizerInput = $"{UniqueString} {nameof(FinalizerInput)}";
+ public const string FinalizerOutput = $"{UniqueString} {nameof(FinalizerOutput)}";
+ public static Exception Finalizer(ref string __instance, ref string __result)
+ {
+ if (__instance == FinalizerInput)
+ {
+ __result = FinalizerOutput;
+ }
+ return null;
+ }
+}
+
+public class ExternalStaticMethod_MathCos_Patch
{
public static IEnumerable Transpiler(IEnumerable instructions) =>
instructions;
diff --git a/HarmonyTests/Patching/NativeDetourPatches.cs b/HarmonyTests/Patching/NativeDetourPatches.cs
index 10b74966..cdb313f4 100644
--- a/HarmonyTests/Patching/NativeDetourPatches.cs
+++ b/HarmonyTests/Patching/NativeDetourPatches.cs
@@ -2,14 +2,68 @@
using HarmonyTests.Patching.Assets;
using NUnit.Framework;
using System;
+using System.IO;
+using System.Reflection;
+using System.Text;
namespace HarmonyLibTests.Patching;
+using EIP = ExternalInstanceMethod_StringIsInterned_Patch;
+
[TestFixture]
public class NativeDetourPatches : TestLogger
{
[Test]
- public void Test_PatchExternalMethod()
+ public void Test_PatchInstanceExternalMethod()
+ {
+ var target = typeof(string).GetMethod("Intern", BindingFlags.Instance|BindingFlags.NonPublic);
+
+ if(target == null)
+ Assert.Inconclusive("string.Intern is missing in current runtime");
+
+#if !NET35
+ if((target.MethodImplementationFlags & MethodImplAttributes.InternalCall) == 0)
+ Assert.Inconclusive("string.Intern is not an InternalCall (extern) in current runtime ");
+#endif
+
+ if(target.GetMethodBody() != null)
+ Assert.Inconclusive("string.Intern has IL body in current runtime");
+
+ var str1 = new StringBuilder().Append('o').Append('k').Append('4').Append('1').ToString();
+ Assert.IsNull(string.IsInterned(str1));
+ var internedStr1 = string.Intern(str1);
+ Assert.AreEqual(internedStr1, string.IsInterned(str1));
+
+ var instance = new Harmony("test-patch-external-instance-method");
+ Assert.NotNull(instance, "Harmony instance");
+
+ instance.Patch(target, transpiler: typeof(EIP).Method("Transpiler"));
+ var str2 = new StringBuilder().Append('o').Append('k').Append('4').Append('2').ToString();
+ Assert.IsNull(string.IsInterned(str2));
+ var internedStr2 = string.Intern(str2);
+ Assert.AreEqual(internedStr2, string.IsInterned(str2));
+
+ instance.Patch(target, prefix: typeof(EIP).Method("Prefix"));
+ Assert.AreEqual(EIP.PrefixOutput, string.Intern(EIP.PrefixInput));
+
+ instance.Patch(target, postfix: typeof(EIP).Method("Postfix"));
+ Assert.AreEqual(EIP.PostfixOutput, string.Intern(EIP.PostfixInput));
+
+ instance.Patch(target, transpiler: typeof(EIP).Method("TranspilerThrow"));
+ Assert.Throws(EIP.TranspiledException, () => string.Intern("does not matter"));
+
+ instance.Patch(target, finalizer: typeof(EIP).Method("Finalizer"));
+ Assert.AreEqual(EIP.FinalizerOutput, string.Intern(EIP.FinalizerInput));
+
+ instance.UnpatchSelf();
+ var str3 = new StringBuilder().Append('o').Append('k').Append('4').Append('3').ToString();
+ Assert.IsNull(string.IsInterned(str3));
+ Assert.AreEqual(internedStr1, string.IsInterned(str1));
+ Assert.AreEqual(internedStr2, string.IsInterned(str2));
+ }
+
+ [Test]
+ public void Test_PatchStaticExternalMethod()
{
var target = SymbolExtensions.GetMethodInfo(() => Math.Cos(0));
@@ -20,22 +74,22 @@ public void Test_PatchExternalMethod()
var cos = Math.Cos;
Assert.AreEqual(1d, cos(0d));
- var instance = new Harmony("test-patch-external-method");
+ var instance = new Harmony("test-patch-external-static-method");
Assert.NotNull(instance, "Harmony instance");
- instance.Patch(target, transpiler: typeof(ExternalMethod_Patch).Method("Transpiler"));
+ instance.Patch(target, transpiler: typeof(ExternalStaticMethod_MathCos_Patch).Method("Transpiler"));
Assert.AreEqual(1d, cos(0d));
- instance.Patch(target, prefix: typeof(ExternalMethod_Patch).Method("Prefix"));
+ instance.Patch(target, prefix: typeof(ExternalStaticMethod_MathCos_Patch).Method("Prefix"));
Assert.AreEqual(1d, cos(2d));
- instance.Patch(target, postfix: typeof(ExternalMethod_Patch).Method("Postfix"));
+ instance.Patch(target, postfix: typeof(ExternalStaticMethod_MathCos_Patch).Method("Postfix"));
Assert.AreEqual(2d, cos(0d));
- instance.Patch(target, transpiler: typeof(ExternalMethod_Patch).Method("TranspilerThrow"));
+ instance.Patch(target, transpiler: typeof(ExternalStaticMethod_MathCos_Patch).Method("TranspilerThrow"));
Assert.Throws(() => cos(0d));
- instance.Patch(target, finalizer: typeof(ExternalMethod_Patch).Method("Finalizer"));
+ instance.Patch(target, finalizer: typeof(ExternalStaticMethod_MathCos_Patch).Method("Finalizer"));
Assert.AreEqual(-2d, cos(0d));
instance.UnpatchSelf();