diff --git a/Sources/ClassMemberInfo.cs b/Sources/ClassMemberInfo.cs new file mode 100644 index 0000000..e6739a5 --- /dev/null +++ b/Sources/ClassMemberInfo.cs @@ -0,0 +1,78 @@ +using System; +using System.Reflection; + +namespace MethodRedirect +{ + public class ClassMemberInfo + { + MethodInfo[] _methods; + public MethodInfo[] Methods + { + get { return _methods; } + } + + private ClassMemberInfo(Type type, string name, bool isMethod, Type[] types = null, bool isStatic = false) + { + BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public; + if (isStatic) + { + flags |= BindingFlags.Static; + } + else + { + flags |= BindingFlags.Instance; + } + + if (isMethod) + { + if (types != null) + { + _methods = new MethodInfo[] { type.GetMethod(name, types) }; + } + else + { + _methods = new MethodInfo[] { type.GetMethod(name, flags) }; + } + } + else + { + var propInfo = type.GetProperty(name, flags); + _methods = new MethodInfo[] { propInfo.GetGetMethod(true), propInfo.GetSetMethod(true) }; + } + } + + private ClassMemberInfo(params MethodInfo[] methods ) + { + _methods = methods; + } + + static public ClassMemberInfo FromMethodInfo(MethodInfo mi) + { + return new ClassMemberInfo(mi); + } + + static public ClassMemberInfo FromFunc(Func target) + { + return new ClassMemberInfo(target.Method); + } + static public ClassMemberInfo FromFunc(Func target) + { + return new ClassMemberInfo(target.Method); + } + + static public ClassMemberInfo FromMethod(Type type, string methodName, Type[] types) + { + return new ClassMemberInfo(type, methodName, true, types); + } + + static public ClassMemberInfo FromMethod(Type type, string methodName, bool isStatic = false) + { + return new ClassMemberInfo(type, methodName, true, null, isStatic); + } + + static public ClassMemberInfo FromProperty(Type type, string methodName, bool isStatic = false) + { + return new ClassMemberInfo(type, methodName, false, null, isStatic); + } + } +} diff --git a/Sources/MethodOperation.cs b/Sources/MethodOperation.cs deleted file mode 100644 index ad9e6ab..0000000 --- a/Sources/MethodOperation.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; - -namespace MethodRedirect -{ - /// - /// Base method operation result - /// - public abstract class MethodOperation : IDisposable - { - public abstract void Restore(); - - public void Dispose() - { - Restore(); - } - } - - /// - /// Result of a method redirection (Origin => *) - /// - public class MethodRedirection : MethodOperation - { - public MethodToken Origin { get; private set; } - - public MethodRedirection(IntPtr address) - { - Origin = new MethodToken(address); - } - - public override void Restore() - { - Origin.Restore(); - } - - public override string ToString() - { - return Origin.ToString(); - } - } -} diff --git a/Sources/MethodRedirect.csproj b/Sources/MethodRedirect.csproj index f53940d..884a9d2 100644 --- a/Sources/MethodRedirect.csproj +++ b/Sources/MethodRedirect.csproj @@ -1,103 +1,99 @@ - - - - - Debug - AnyCPU - {2A84DC12-471E-492F-B390-DF3ADB452E28} - Exe - MethodRedirect - MethodRedirect - v4.0 - 512 - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - MethodRedirect.Scenario1 - - - true - bin\x64\Debug\ - DEBUG;TRACE - true - full - x64 - 7.3 - prompt - false - - - bin\x64\Release\ - TRACE - true - true - pdbonly - x64 - 7.3 - prompt - - - true - bin\x86\Debug\ - DEBUG;TRACE - true - full - x86 - 7.3 - prompt - - - bin\x86\Release\ - TRACE - true - false - pdbonly - x86 - 7.3 - prompt - true - - - - - - - - - - - - - - - - - - - - - - - + + + + + Debug + AnyCPU + {2A84DC12-471E-492F-B390-DF3ADB452E28} + Library + MethodRedirect + MethodRedirect + v4.0 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + + + + + true + bin\x64\Debug\ + DEBUG;TRACE + true + full + x64 + 7.3 + prompt + false + + + bin\x64\Release\ + TRACE + true + true + pdbonly + x64 + 7.3 + prompt + + + true + bin\x86\Debug\ + DEBUG;TRACE + true + full + x86 + 7.3 + prompt + + + bin\x86\Release\ + TRACE + true + false + pdbonly + x86 + 7.3 + prompt + true + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Sources/MethodToken.cs b/Sources/MethodToken.cs index 69237d5..9b419ae 100644 --- a/Sources/MethodToken.cs +++ b/Sources/MethodToken.cs @@ -6,26 +6,28 @@ namespace MethodRedirect public struct MethodToken : IDisposable { public IntPtr Address { get; private set; } - public IntPtr Value { get; private set; } + public IntPtr OriginalValue { get; private set; } + public IntPtr TargetValue { get; private set; } - public MethodToken(IntPtr address) + public MethodToken(IntPtr address, IntPtr targetValue) { // On token creation, preserve the address and the current value at this address Address = address; - Value = Marshal.ReadIntPtr(address); + OriginalValue = Marshal.ReadIntPtr(address); + TargetValue = targetValue; } public void Restore() { // Restore the value at the address - Marshal.Copy(new IntPtr[] { Value }, 0, Address, 1); + Marshal.Copy(new IntPtr[] { OriginalValue }, 0, Address, 1); } public override string ToString() { IntPtr met = Address; IntPtr tar = Marshal.ReadIntPtr(Address); - IntPtr ori = Value; + IntPtr ori = OriginalValue; return "Method address = " + met.ToString("x").PadLeft(8, '0') + "\n" + "Target address = " + tar.ToString("x").PadLeft(8, '0') + "\n" + diff --git a/Sources/Extensions.cs b/Sources/MethodUtil.cs similarity index 71% rename from Sources/Extensions.cs rename to Sources/MethodUtil.cs index 80b1ddc..1174495 100644 --- a/Sources/Extensions.cs +++ b/Sources/MethodUtil.cs @@ -1,18 +1,71 @@ using System; using System.Diagnostics; +using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace MethodRedirect { - static class Extensions + static class MethodUtil { - public static MethodOperation RedirectTo(this MethodInfo origin, Func target, bool verbose = false) - => RedirectTo(origin, target.Method, verbose); - - public static MethodOperation RedirectTo(this MethodInfo origin, Func target, bool verbose = false) - => RedirectTo(origin, target.Method, verbose); + /// + /// Hooks method from -type method name . Method arguments + /// must be identical to the one used in + /// + public static OriginalMethodsInfo HookMethod(Type origType, string methodName, Delegate hook) + { + var invokeMeth = hook.GetType().GetMethod("Invoke"); + var methodArgs = invokeMeth.GetParameters().Select(x => x.ParameterType).ToArray(); + return HookMethod(ClassMemberInfo.FromMethod(origType, methodName, methodArgs), ClassMemberInfo.FromMethodInfo(hook.Method)); + } + + /// + /// Hooks method from -type to -type with method names + /// . Methods argument types must match to each other. + /// + /// If method cannot be found in origType. + public static OriginalMethodsInfo HookMethod(Type origType, Type hookType, string methodName) + { + OriginalMethodsInfo origins = new OriginalMethodsInfo(); + var hookMethods = hookType.GetMethods(BindingFlags.Instance | + BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).Where(x => x.Name == methodName); + + foreach (var hookMethod in hookMethods) + { + var callTypes = hookMethod.GetParameters().Select(x => x.ParameterType).ToList(); + if (hookMethod.IsStatic) + { + callTypes.RemoveAt(0); + } + + var origMethod = origType.GetMethod(methodName, callTypes.ToArray()); + if (origMethod == null) + { + string argTypes = String.Join(",", callTypes.Select(x => x.FullName)); + throw new ArgumentException($"Hook method {methodName}({argTypes}) does not have mapping in original type"); + } + + RedirectTo(origins, origMethod, hookMethod); + } + + return origins; + } + + + public static OriginalMethodsInfo HookMethod(ClassMemberInfo orig, ClassMemberInfo hook) + { + OriginalMethodsInfo origins = new OriginalMethodsInfo(); + var origMethods = orig.Methods; + var hookMethods = hook.Methods; + + for (int i = 0; i < Math.Min(origMethods.Length, hookMethods.Length); i++) + { + RedirectTo(origins, origMethods[i], hookMethods[i]); + } + + return origins; + } /// /// Redirect origin method calls to the specified target method. @@ -21,11 +74,18 @@ public static MethodOperation RedirectTo(this MethodInfo origin, Func /// /// - /// - /// A MethodRedirection operation result object - /// - public static MethodOperation RedirectTo(this MethodInfo origin, MethodInfo target, bool verbose = false) + static void RedirectTo(OriginalMethodsInfo origins, MethodInfo origin, MethodInfo target) { + if (origin == null) + { + throw new ArgumentNullException("origin"); + } + + if (target == null) + { + throw new ArgumentNullException("target"); + } + IntPtr ori = GetMethodAddress(origin); IntPtr tar = GetMethodAddress(target); @@ -34,19 +94,7 @@ public static MethodOperation RedirectTo(this MethodInfo origin, MethodInfo targ Debug.Assert(Marshal.ReadIntPtr(ori) == origin.MethodHandle.GetFunctionPointer()); Debug.Assert(Marshal.ReadIntPtr(tar) == target.MethodHandle.GetFunctionPointer()); - if (verbose) - { - Console.WriteLine("\nPlatform : {0}", IntPtr.Size == 4 ? "x86" : "x64"); - Console.WriteLine("IntPtr.Size : {0}", IntPtr.Size); - - Console.WriteLine("\nFrom origin method : {0}", origin.Name); - OutputMethodDetails(origin, ori); - - Console.WriteLine("\nTo target method : {0}", target.Name); - OutputMethodDetails(target, tar); - } - - return Redirect(ori, tar, verbose); + Redirect(origins, ori, tar); } /// @@ -56,7 +104,7 @@ public static MethodOperation RedirectTo(this MethodInfo origin, MethodInfo targ /// /// The unconditional jmp address to the JIT-compiled method /// - private static void OutputMethodDetails(MethodInfo mi, IntPtr address) + static void OutputMethodDetails(MethodInfo mi, IntPtr address) { IntPtr mt = mi.DeclaringType.TypeHandle.Value; // MethodTable address IntPtr md = mi.MethodHandle.Value; // MethodDescriptor address @@ -141,7 +189,7 @@ private static void OutputMethodDetails(MethodInfo mi, IntPtr address) /// - the CodeOrIL field contains the Virtual Address (VA) of the JIT-compiled method. /// /// The JITed method address - private static IntPtr GetMethodAddress(MethodInfo mi) + static IntPtr GetMethodAddress(MethodInfo mi) { const ushort SLOT_NUMBER_MASK = 0xffff; // 2 bytes mask const int MT_OFFSET_32BIT = 0x28; // 40 bytes offset @@ -183,25 +231,13 @@ private static IntPtr GetMethodAddress(MethodInfo mi) return address; } - private static MethodRedirection Redirect(IntPtr ori, IntPtr tar, bool verbose) + static void Redirect(OriginalMethodsInfo origins, IntPtr ori, IntPtr tar) { + origins.AddOrigin(ori, tar); + // Must create the token before address is assigned - var token = new MethodRedirection(ori); - - if (verbose) - { - Console.WriteLine("\nRedirect..."); - Console.WriteLine("From {0} [{1}] => To {2} [{3}]", - ori.ToString("x").PadLeft(8, '0'), - Marshal.ReadIntPtr(ori).ToString("x").PadLeft(8, '0'), - tar.ToString("x").PadLeft(8, '0'), - Marshal.ReadIntPtr(tar).ToString("x").PadLeft(8, '0')); - } - // Redirect origin method to target method Marshal.Copy(new IntPtr[] { Marshal.ReadIntPtr(tar) }, 0, ori, 1); - - return token; } } } diff --git a/Sources/OriginalMethodsInfo.cs b/Sources/OriginalMethodsInfo.cs new file mode 100644 index 0000000..896a200 --- /dev/null +++ b/Sources/OriginalMethodsInfo.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MethodRedirect +{ + /// + /// Result of a method redirection (Origin => *) + /// + public class OriginalMethodsInfo : IDisposable + { + List Origins = new List(); + + public void Dispose() + { + Restore(); + } + + /// + /// Release method hook without patching back original code + /// + public void Release() + { + Origins.Clear(); + } + + public void Restore() + { + Origins.ForEach(x => x.Restore()); + Origins.Clear(); + } + + public void AddOrigin(IntPtr address, IntPtr targetValue) + { + Origins.Add(new MethodToken(address, targetValue)); + } + + public override string ToString() + { + return Origins.First().ToString(); + } + + /// + /// Restores original method values so it can be called successfully without recursive crash, when disposing + /// return value - values will be patched to original values. + /// + public OriginalMethodsInfo RestoreOriginal() + { + OriginalMethodsInfo methodsInfo = new OriginalMethodsInfo(); + foreach( MethodToken token in Origins) + { + token.Restore(); + methodsInfo.Origins.Add(new MethodToken(token.Address, token.TargetValue)); + } + + return methodsInfo; + } + } +} diff --git a/Sources/Scenario2.cs b/Sources/Scenario2.cs deleted file mode 100644 index 543421f..0000000 --- a/Sources/Scenario2.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Diagnostics; -using System.Reflection; - -namespace MethodRedirect -{ - class Scenario2 - { - static void Main(string[] args) - { - Console.WriteLine("Redirect : MethodRedirect.Scenario2.InternalVirtualInstanceMethod()"); - Console.WriteLine("To : MethodRedirect.Scenario2.PrivateInstanceMethod()"); - - Assembly assembly = Assembly.GetAssembly(typeof(Scenario2)); - Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario2"); - - MethodInfo Scenario_InternalVirtualInstanceMethod = Scenario_Type.GetMethod("InternalVirtualInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic); - MethodInfo Scenario_PrivateInstanceMethod = Scenario_Type.GetMethod("PrivateInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic); - - var token = Scenario_InternalVirtualInstanceMethod.RedirectTo(Scenario_PrivateInstanceMethod, true); - - var scenario = (Scenario2)Activator.CreateInstance(Scenario_Type); - - string methodName = scenario.InternalVirtualInstanceMethod(); - - Console.WriteLine("Call MethodRedirect.Scenario2.InternalVirtualInstanceMethod => {0}", methodName); - - Debug.Assert(methodName == "MethodRedirect.Scenario2.PrivateInstanceMethod"); - - if (methodName == "MethodRedirect.Scenario2.PrivateInstanceMethod") - { - Console.WriteLine("\nRestore..."); - - token.Restore(); - - methodName = scenario.InternalVirtualInstanceMethod(); - - Console.WriteLine("Call MethodRedirect.Scenario2.InternalVirtualInstanceMethod => {0}", methodName); - - Debug.Assert(methodName == "MethodRedirect.Scenario2.InternalVirtualInstanceMethod"); - - if (methodName == "MethodRedirect.Scenario2.InternalVirtualInstanceMethod") - { - Console.WriteLine("\nSUCCESS!"); - } - else - { - Console.WriteLine("\nRestore FAILED"); - } - } - else - { - Console.WriteLine("\nRedirection FAILED"); - } - - Console.ReadKey(); - } - - internal virtual string AnotherInternalVirtualInstanceMethod() - { - return "MethodRedirect.Scenario2.AnotherInternalVirtualInstanceMethod"; - } - - internal virtual string InternalVirtualInstanceMethod() - { - return "MethodRedirect.Scenario2.InternalVirtualInstanceMethod"; - } - - private string PrivateInstanceMethod() - { - return "MethodRedirect.Scenario2.PrivateInstanceMethod"; - } - } -} diff --git a/UnitTests/MethodRedirect_UT.csproj b/UnitTests/MethodRedirect_UT.csproj index 29b3810..4af34d2 100644 --- a/UnitTests/MethodRedirect_UT.csproj +++ b/UnitTests/MethodRedirect_UT.csproj @@ -1,106 +1,114 @@ - - - - - Debug - AnyCPU - {44352427-E1E0-4679-BBBD-0E298B2BAEB4} - Library - Properties - MethodRedirect_UT - MethodRedirect_UT - v4.5 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 15.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - bin\x86\Debug\ - DEBUG;TRACE - full - x86 - 7.3 - prompt - - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - 7.3 - prompt - - - true - bin\x64\Debug\ - DEBUG;TRACE - full - x64 - 7.3 - prompt - - - bin\x64\Release\ - TRACE - true - pdbonly - x64 - 7.3 - prompt - - - - - - - - - - - - - - - - - - 2.1.2 - - - 2.1.2 - - - - - {2a84dc12-471e-492f-b390-df3adb452e28} - MethodRedirect - - - - + + + + + Debug + AnyCPU + {44352427-E1E0-4679-BBBD-0E298B2BAEB4} + Library + Properties + MethodRedirect_UT + MethodRedirect_UT + v4.5 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + 7.3 + prompt + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + 7.3 + prompt + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + 7.3 + prompt + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + 7.3 + prompt + + + + + + + + + + + + + + + + + + + + + + + + + + 2.1.2 + + + 2.1.2 + + + + + {2a84dc12-471e-492f-b390-df3adb452e28} + MethodRedirect + + + + \ No newline at end of file diff --git a/Sources/Scenario1.cs b/UnitTests/Scenario1.cs similarity index 55% rename from Sources/Scenario1.cs rename to UnitTests/Scenario1.cs index 1c16fa2..504246f 100644 --- a/Sources/Scenario1.cs +++ b/UnitTests/Scenario1.cs @@ -1,23 +1,20 @@ -using System; +using MethodRedirect; +using System; using System.Diagnostics; using System.Reflection; -namespace MethodRedirect +namespace Scenarios_UT { class Scenario1 { static void Main(string[] args) { - Console.WriteLine("Redirect : MethodRedirect.Scenario1.InternalInstanceMethod()"); - Console.WriteLine("To : MethodRedirect.Scenario1.PrivateInstanceMethod()"); + Type Scenario_Type = typeof(Scenario1); - Assembly assembly = Assembly.GetAssembly(typeof(Scenario1)); - Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario1"); - - MethodInfo Scenario_InternalInstanceMethod = Scenario_Type.GetMethod("InternalInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic); - MethodInfo Scenario_PrivateInstanceMethod = Scenario_Type.GetMethod("PrivateInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic); - - var token = Scenario_InternalInstanceMethod.RedirectTo(Scenario_PrivateInstanceMethod, true); + var token = MethodUtil.HookMethod( + ClassMemberInfo.FromMethod(Scenario_Type, "InternalInstanceMethod"), + ClassMemberInfo.FromMethod(Scenario_Type, "PrivateInstanceMethod") + ); // Using "dynamic" type to resolve the following issue in x64 and Release (with code optimizations) builds. // @@ -34,25 +31,25 @@ static void Main(string[] args) string methodName = scenario.InternalInstanceMethod(); - Console.WriteLine("Call MethodRedirect.Scenario1.InternalInstanceMethod => {0}", methodName); + //Console.WriteLine("Call Scenarios_UT.Scenario1.InternalInstanceMethod => {0}", methodName); - Debug.Assert(methodName == "MethodRedirect.Scenario1.PrivateInstanceMethod"); + //Debug.Assert(methodName == "Scenarios_UT.Scenario1.PrivateInstanceMethod"); - if (methodName == "MethodRedirect.Scenario1.PrivateInstanceMethod") + if (methodName == "Scenarios_UT.Scenario1.PrivateInstanceMethod") { - Console.WriteLine("\nRestore..."); + //Console.WriteLine("\nRestore..."); token.Restore(); - Console.WriteLine(token); + //Console.WriteLine(token); methodName = scenario.InternalInstanceMethod(); - Console.WriteLine("Call MethodRedirect.Scenario1.InternalInstanceMethod => {0}", methodName); + //Console.WriteLine("Call Scenarios_UT.Scenario1.InternalInstanceMethod => {0}", methodName); - Debug.Assert(methodName == "MethodRedirect.Scenario1.InternalInstanceMethod"); + //Debug.Assert(methodName == "Scenarios_UT.Scenario1.InternalInstanceMethod"); - if (methodName == "MethodRedirect.Scenario1.InternalInstanceMethod") + if (methodName == "Scenarios_UT.Scenario1.InternalInstanceMethod") { Console.WriteLine("\nSUCCESS!"); } @@ -66,27 +63,27 @@ static void Main(string[] args) Console.WriteLine("\nRedirection FAILED"); } - Console.ReadKey(); + //Console.ReadKey(); } internal string InternalInstanceMethod() { - return "MethodRedirect.Scenario1.InternalInstanceMethod"; + return "Scenarios_UT.Scenario1.InternalInstanceMethod"; } private string PrivateInstanceMethod() { - return "MethodRedirect.Scenario1.PrivateInstanceMethod"; + return "Scenarios_UT.Scenario1.PrivateInstanceMethod"; } public string PublicInstanceMethod() { - return "MethodRedirect.Scenario1.PublicInstanceMethod"; + return "Scenarios_UT.Scenario1.PublicInstanceMethod"; } public static string PublicStaticMethod() { - return "MethodRedirect.Scenario1.PublicStaticMethod"; + return "Scenarios_UT.Scenario1.PublicStaticMethod"; } } } diff --git a/UnitTests/Scenario1_UnitTests.cs b/UnitTests/Scenario1_UnitTests.cs index 12522a5..b698830 100644 --- a/UnitTests/Scenario1_UnitTests.cs +++ b/UnitTests/Scenario1_UnitTests.cs @@ -11,76 +11,73 @@ public class Scenario1_UnitTests [TestMethod] public void Redirect_InternalInstanceMethod_To_PrivateInstanceMethod_SameInstance() { - Assembly assembly = Assembly.GetAssembly(typeof(Scenario1)); - Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario1"); - - MethodInfo Scenario_InternalInstanceMethod = Scenario_Type.GetMethod("InternalInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic); - MethodInfo Scenario_PrivateInstanceMethod = Scenario_Type.GetMethod("PrivateInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic); - - var token = Scenario_InternalInstanceMethod.RedirectTo(Scenario_PrivateInstanceMethod); + Type Scenario_Type = typeof(Scenario1); + var token = MethodUtil.HookMethod( + ClassMemberInfo.FromMethod(Scenario_Type, "InternalInstanceMethod"), + ClassMemberInfo.FromMethod(Scenario_Type, "PrivateInstanceMethod") + ); + // Using "dynamic" type to prevent caching the first call result and make the second assert fail dynamic scenario = (Scenario1)Activator.CreateInstance(Scenario_Type); string methodName = scenario.InternalInstanceMethod(); - Assert.IsTrue(methodName == "MethodRedirect.Scenario1.PrivateInstanceMethod"); + Assert.IsTrue(methodName == "Scenarios_UT.Scenario1.PrivateInstanceMethod"); token.Restore(); methodName = scenario.InternalInstanceMethod(); - Assert.IsTrue(methodName == "MethodRedirect.Scenario1.InternalInstanceMethod"); + Assert.IsTrue(methodName == "Scenarios_UT.Scenario1.InternalInstanceMethod"); } [TestMethod] public void Redirect_PublicInstanceMethod_To_PrivateInstanceMethod_SameInstance() { - Assembly assembly = Assembly.GetAssembly(typeof(Scenario1)); - Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario1"); - - MethodInfo Scenario_PublicInstanceMethod = Scenario_Type.GetMethod("PublicInstanceMethod", BindingFlags.Instance | BindingFlags.Public); - MethodInfo Scenario_PrivateInstanceMethod = Scenario_Type.GetMethod("PrivateInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic); - - var token = Scenario_PublicInstanceMethod.RedirectTo(Scenario_PrivateInstanceMethod); + Type Scenario_Type = typeof(Scenario1); + var token = MethodUtil.HookMethod( + ClassMemberInfo.FromMethod(Scenario_Type, "PublicInstanceMethod"), + ClassMemberInfo.FromMethod(Scenario_Type, "PrivateInstanceMethod") + ); + // Using "dynamic" type to prevent caching the first call result and make the second assert fail dynamic scenario = (Scenario1)Activator.CreateInstance(Scenario_Type); string methodName = scenario.PublicInstanceMethod(); - Assert.IsTrue(methodName == "MethodRedirect.Scenario1.PrivateInstanceMethod"); + Assert.IsTrue(methodName == "Scenarios_UT.Scenario1.PrivateInstanceMethod"); token.Restore(); methodName = scenario.PublicInstanceMethod(); - Assert.IsTrue(methodName == "MethodRedirect.Scenario1.PublicInstanceMethod"); + Assert.IsTrue(methodName == "Scenarios_UT.Scenario1.PublicInstanceMethod"); } [TestMethod] public void Redirect_PublicInstanceMethod_To_PublicStaticMethod_SameInstance() { - Assembly assembly = Assembly.GetAssembly(typeof(Scenario1)); - Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario1"); - - MethodInfo Scenario_PublicInstanceMethod = Scenario_Type.GetMethod("PublicInstanceMethod", BindingFlags.Instance | BindingFlags.Public); - MethodInfo Scenario_PublicStaticMethod = Scenario_Type.GetMethod("PublicStaticMethod", BindingFlags.Static | BindingFlags.Public); + Type Scenario_Type = typeof(Scenario1); - var token = Scenario_PublicInstanceMethod.RedirectTo(Scenario_PublicStaticMethod); + var token = MethodUtil.HookMethod( + ClassMemberInfo.FromMethod(Scenario_Type, "PublicInstanceMethod"), + ClassMemberInfo.FromMethod(Scenario_Type, "PublicStaticMethod", true) + ); // Using "dynamic" type to prevent caching the first call result and make the second assert fail dynamic scenario = (Scenario1)Activator.CreateInstance(Scenario_Type); string methodName = scenario.PublicInstanceMethod(); - Assert.IsTrue(methodName == "MethodRedirect.Scenario1.PublicStaticMethod"); + Assert.IsTrue(methodName == "Scenarios_UT.Scenario1.PublicStaticMethod"); token.Restore(); methodName = scenario.PublicInstanceMethod(); - Assert.IsTrue(methodName == "MethodRedirect.Scenario1.PublicInstanceMethod"); + Assert.IsTrue(methodName == "Scenarios_UT.Scenario1.PublicInstanceMethod"); } } } diff --git a/UnitTests/Scenario2.cs b/UnitTests/Scenario2.cs new file mode 100644 index 0000000..f1ae025 --- /dev/null +++ b/UnitTests/Scenario2.cs @@ -0,0 +1,74 @@ +using MethodRedirect; +using System; +using System.Diagnostics; +using System.Reflection; + +namespace Scenarios_UT +{ + class Scenario2 + { + static void Main(string[] args) + { + Console.WriteLine("Redirect : Scenarios_UT.Scenario2.InternalVirtualInstanceMethod()"); + Console.WriteLine("To : Scenarios_UT.Scenario2.PrivateInstanceMethod()"); + + Type Scenario_Type = typeof(Scenario2); + + var token = MethodUtil.HookMethod( + ClassMemberInfo.FromMethod(Scenario_Type, "InternalVirtualInstanceMethod"), + ClassMemberInfo.FromMethod(Scenario_Type, "PrivateInstanceMethod") + ); + + var scenario = (Scenario2)Activator.CreateInstance(Scenario_Type); + + string methodName = scenario.InternalVirtualInstanceMethod(); + + Console.WriteLine("Call Scenarios_UT.Scenario2.InternalVirtualInstanceMethod => {0}", methodName); + + Debug.Assert(methodName == "Scenarios_UT.Scenario2.PrivateInstanceMethod"); + + if (methodName == "Scenarios_UT.Scenario2.PrivateInstanceMethod") + { + Console.WriteLine("\nRestore..."); + + token.Restore(); + + methodName = scenario.InternalVirtualInstanceMethod(); + + Console.WriteLine("Call Scenarios_UT.Scenario2.InternalVirtualInstanceMethod => {0}", methodName); + + Debug.Assert(methodName == "Scenarios_UT.Scenario2.InternalVirtualInstanceMethod"); + + if (methodName == "Scenarios_UT.Scenario2.InternalVirtualInstanceMethod") + { + Console.WriteLine("\nSUCCESS!"); + } + else + { + Console.WriteLine("\nRestore FAILED"); + } + } + else + { + Console.WriteLine("\nRedirection FAILED"); + } + + Console.ReadKey(); + } + + internal virtual string AnotherInternalVirtualInstanceMethod() + { + return "Scenarios_UT.Scenario2.AnotherInternalVirtualInstanceMethod"; + } + + internal virtual string InternalVirtualInstanceMethod() + { + return "Scenarios_UT.Scenario2.InternalVirtualInstanceMethod"; + } + + private string PrivateInstanceMethod() + { + return "Scenarios_UT.Scenario2.PrivateInstanceMethod"; + } + } +} diff --git a/UnitTests/Scenario2_UnitTests.cs b/UnitTests/Scenario2_UnitTests.cs index 4952a6d..ec98c9f 100644 --- a/UnitTests/Scenario2_UnitTests.cs +++ b/UnitTests/Scenario2_UnitTests.cs @@ -11,25 +11,24 @@ public class Scenario2_UnitTests [TestMethod] public void Redirect_InternalVirtualInstanceMethod_To_PrivateInstanceMethod_SameInstance() { - Assembly assembly = Assembly.GetAssembly(typeof(Scenario2)); - Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario2"); + Type Scenario_Type = typeof(Scenario2); - MethodInfo Scenario_InternalVirtualInstanceMethod = Scenario_Type.GetMethod("InternalVirtualInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic); - MethodInfo Scenario_PrivateInstanceMethod = Scenario_Type.GetMethod("PrivateInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic); - - var token = Scenario_InternalVirtualInstanceMethod.RedirectTo(Scenario_PrivateInstanceMethod); + var token = MethodUtil.HookMethod( + ClassMemberInfo.FromMethod(Scenario_Type, "InternalVirtualInstanceMethod"), + ClassMemberInfo.FromMethod(Scenario_Type, "PrivateInstanceMethod") + ); var scenario = (Scenario2)Activator.CreateInstance(Scenario_Type); string methodName = scenario.InternalVirtualInstanceMethod(); - Assert.IsTrue(methodName == "MethodRedirect.Scenario2.PrivateInstanceMethod"); + Assert.IsTrue(methodName == "Scenarios_UT.Scenario2.PrivateInstanceMethod"); token.Restore(); methodName = scenario.InternalVirtualInstanceMethod(); - Assert.IsTrue(methodName == "MethodRedirect.Scenario2.InternalVirtualInstanceMethod"); + Assert.IsTrue(methodName == "Scenarios_UT.Scenario2.InternalVirtualInstanceMethod"); } } } diff --git a/Sources/Scenario3.cs b/UnitTests/Scenario3.cs similarity index 66% rename from Sources/Scenario3.cs rename to UnitTests/Scenario3.cs index fe1d137..8a8470c 100644 --- a/Sources/Scenario3.cs +++ b/UnitTests/Scenario3.cs @@ -1,6 +1,6 @@ using System; -namespace MethodRedirect +namespace Scenarios_UT { class Scenario3 { @@ -11,7 +11,7 @@ static void Main(string[] args) internal virtual string InternalVirtualInstanceMethod() { - return "MethodRedirect.Scenario3.InternalVirtualInstanceMethod"; + return "Scenarios_UT.Scenario3.InternalVirtualInstanceMethod"; } } @@ -19,7 +19,7 @@ class Scenario3Ext { internal static string InternalStaticMethod() { - return "MethodRedirect.Scenario3Ext.InternalStaticMethod"; + return "Scenarios_UT.Scenario3Ext.InternalStaticMethod"; } } } diff --git a/UnitTests/Scenario3_UnitTests.cs b/UnitTests/Scenario3_UnitTests.cs index 6583cbd..8054543 100644 --- a/UnitTests/Scenario3_UnitTests.cs +++ b/UnitTests/Scenario3_UnitTests.cs @@ -11,26 +11,24 @@ public class Scenario3_UnitTests [TestMethod] public void Redirect_InternalVirtualInstanceMethod_To_InternalStaticMethod_DifferentInstance() { - Assembly assembly = Assembly.GetAssembly(typeof(Scenario3)); - Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario3"); - Type ScenarioExt_Type = assembly.GetType("MethodRedirect.Scenario3Ext"); + Type Scenario_Type = typeof(Scenario3); - MethodInfo Scenario_InternalVirtualInstanceMethod = Scenario_Type.GetMethod("InternalVirtualInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic); - MethodInfo ScenarioExt_InternalStaticMethod = ScenarioExt_Type.GetMethod("InternalStaticMethod", BindingFlags.Static | BindingFlags.NonPublic); - - var token = Scenario_InternalVirtualInstanceMethod.RedirectTo(ScenarioExt_InternalStaticMethod); + var token = MethodUtil.HookMethod( + ClassMemberInfo.FromMethod(Scenario_Type, "InternalVirtualInstanceMethod"), + ClassMemberInfo.FromMethod(typeof(Scenario3Ext), "InternalStaticMethod", true) + ); var scenario = (Scenario3)Activator.CreateInstance(Scenario_Type); string methodName = scenario.InternalVirtualInstanceMethod(); - Assert.IsTrue(methodName == "MethodRedirect.Scenario3Ext.InternalStaticMethod"); + Assert.IsTrue(methodName == "Scenarios_UT.Scenario3Ext.InternalStaticMethod"); token.Restore(); methodName = scenario.InternalVirtualInstanceMethod(); - Assert.IsTrue(methodName == "MethodRedirect.Scenario3.InternalVirtualInstanceMethod"); + Assert.IsTrue(methodName == "Scenarios_UT.Scenario3.InternalVirtualInstanceMethod"); } } } diff --git a/Sources/Scenario4.cs b/UnitTests/Scenario4.cs similarity index 65% rename from Sources/Scenario4.cs rename to UnitTests/Scenario4.cs index 5af1f27..c15014e 100644 --- a/Sources/Scenario4.cs +++ b/UnitTests/Scenario4.cs @@ -1,6 +1,6 @@ using System; -namespace MethodRedirect +namespace Scenarios_UT { class Scenario4 { @@ -11,7 +11,7 @@ static void Main(string[] args) public virtual string PublicVirtualInstanceMethod() { - return "MethodRedirect.Scenario4.PublicVirtualInstanceMethod"; + return "Scenarios_UT.Scenario4.PublicVirtualInstanceMethod"; } } @@ -19,7 +19,7 @@ class Scenario4Ext { private string PrivateInstanceMethod() { - return "MethodRedirect.Scenario4Ext.PrivateInstanceMethod"; + return "Scenarios_UT.Scenario4Ext.PrivateInstanceMethod"; } } } diff --git a/UnitTests/Scenario4_UnitTests.cs b/UnitTests/Scenario4_UnitTests.cs index 85dfe9c..9a3e01c 100644 --- a/UnitTests/Scenario4_UnitTests.cs +++ b/UnitTests/Scenario4_UnitTests.cs @@ -11,26 +11,24 @@ public class Scenario4_UnitTests [TestMethod] public void Redirect_PublicVirtualInstanceMethod_To_PrivateInstanceMethod_DifferentInstance() { - Assembly assembly = Assembly.GetAssembly(typeof(Scenario4)); - Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario4"); - Type ScenarioExt_Type = assembly.GetType("MethodRedirect.Scenario4Ext"); + Type Scenario_Type = typeof(Scenario4); - MethodInfo Scenario_PublicVirtualInstanceMethod = Scenario_Type.GetMethod("PublicVirtualInstanceMethod", BindingFlags.Instance | BindingFlags.Public); - MethodInfo ScenarioExt_PrivateInstanceMethod = ScenarioExt_Type.GetMethod("PrivateInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic); - - var token = Scenario_PublicVirtualInstanceMethod.RedirectTo(ScenarioExt_PrivateInstanceMethod); + var token = MethodUtil.HookMethod( + ClassMemberInfo.FromMethod(Scenario_Type, "PublicVirtualInstanceMethod"), + ClassMemberInfo.FromMethod(typeof(Scenario4Ext), "PrivateInstanceMethod") + ); var scenario = (Scenario4)Activator.CreateInstance(Scenario_Type); string methodName = scenario.PublicVirtualInstanceMethod(); - Assert.IsTrue(methodName == "MethodRedirect.Scenario4Ext.PrivateInstanceMethod"); + Assert.IsTrue(methodName == "Scenarios_UT.Scenario4Ext.PrivateInstanceMethod"); token.Restore(); methodName = scenario.PublicVirtualInstanceMethod(); - Assert.IsTrue(methodName == "MethodRedirect.Scenario4.PublicVirtualInstanceMethod"); + Assert.IsTrue(methodName == "Scenarios_UT.Scenario4.PublicVirtualInstanceMethod"); } } } diff --git a/Sources/Scenario5.cs b/UnitTests/Scenario5.cs similarity index 97% rename from Sources/Scenario5.cs rename to UnitTests/Scenario5.cs index 86df96e..0b9697a 100644 --- a/Sources/Scenario5.cs +++ b/UnitTests/Scenario5.cs @@ -1,6 +1,6 @@ using System; -namespace MethodRedirect +namespace Scenarios_UT { class Scenario5: Scenario5Base { diff --git a/UnitTests/Scenario5_UnitTests.cs b/UnitTests/Scenario5_UnitTests.cs index 1229e64..70efa1b 100644 --- a/UnitTests/Scenario5_UnitTests.cs +++ b/UnitTests/Scenario5_UnitTests.cs @@ -11,27 +11,21 @@ public class Scenario5_UnitTests [TestMethod] public void Redirect_PrivateAccessorMethods_To_PublicAccessorMethods_DerivedInstance() { - Assembly assembly = Assembly.GetAssembly(typeof(Scenario5)); - Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario5"); - Type ScenarioBase_Type = assembly.GetType("MethodRedirect.Scenario5Base"); - - PropertyInfo Scenario_CustomFeeProperty = Scenario_Type.GetProperty("CustomFee", BindingFlags.Instance | BindingFlags.Public); + Type Scenario_Type = typeof(Scenario5); + Assembly assembly = Assembly.GetAssembly(typeof(Scenario5)); + Type ScenarioBase_Type = assembly.GetType("Scenarios_UT.Scenario5Base"); + PropertyInfo ScenarioBase_MinimumFeeProperty = ScenarioBase_Type.GetProperty("MinimumFee", BindingFlags.Instance | BindingFlags.NonPublic); - // Obtain public accessor methods from main class - MethodInfo Scenario_PublicGetCustomFeeMethod = Scenario_CustomFeeProperty.GetGetMethod(); - MethodInfo Scenario_PublicSetCustomFeeMethod = Scenario_CustomFeeProperty.GetSetMethod(); - - // Obtain private accessor methods from base class + //// Obtain private accessor methods from base class MethodInfo ScenarioBase_PrivateGetMinimumFeeMethod = ScenarioBase_MinimumFeeProperty.GetGetMethod(true); // "true" for private accessor - MethodInfo ScenarioBase_PrivateSetMinimumFeeMethod = ScenarioBase_MinimumFeeProperty.GetSetMethod(true); // "true" for private accessor FieldInfo ScenarioBase_PrivateMinimumFeeField = ScenarioBase_Type.GetField("_minimumFee", BindingFlags.Instance | BindingFlags.NonPublic); - // Redirect the base class' private property accessors with the main's class public ones - // Important: this must be done BEFORE creating an instance of the scenario otherwise the redirected addresses won't be set correctly. - var tokenGet = ScenarioBase_PrivateGetMinimumFeeMethod.RedirectTo(Scenario_PublicGetCustomFeeMethod); - var tokenSet = ScenarioBase_PrivateSetMinimumFeeMethod.RedirectTo(Scenario_PublicSetCustomFeeMethod); + var token = MethodUtil.HookMethod( + ClassMemberInfo.FromProperty(typeof(Scenario5Base), "MinimumFee"), + ClassMemberInfo.FromProperty(typeof(Scenario5), "CustomFee") + ); // Create instance of scenario var scenario = new Scenario5(); // (Scenario5)Activator.CreateInstance(Scenario_Type); @@ -47,18 +41,17 @@ public void Redirect_PrivateAccessorMethods_To_PublicAccessorMethods_DerivedInst Assert.IsTrue(scenario.CustomFee == 0.21); // The custom fee should now be 21% // Test "Get" accessor from base class - double customFee = (double) ScenarioBase_PrivateGetMinimumFeeMethod.Invoke(scenario, null); + double customFee = scenario.CustomFee; Assert.IsTrue(customFee == 0.21); // The fee value should be the same value as the same redirected accessor was called // Another verification is to check the private member value of the base class - double minimumFee = (double) ScenarioBase_PrivateMinimumFeeField.GetValue(scenario); + double minimumFee = (double)ScenarioBase_PrivateMinimumFeeField.GetValue(scenario); Assert.IsTrue(minimumFee == 0.1); // Should be the default 10% field fee // Restoring accessors methods to original addresses - tokenGet.Restore(); - tokenSet.Restore(); + token.Restore(); // The base method addresses have been restored, for example a call to the private "Get" // accessor of the base class should return the original value diff --git a/Sources/Scenario6.cs b/UnitTests/Scenario6.cs similarity index 66% rename from Sources/Scenario6.cs rename to UnitTests/Scenario6.cs index 224e475..c3e7b9c 100644 --- a/Sources/Scenario6.cs +++ b/UnitTests/Scenario6.cs @@ -1,6 +1,6 @@ using System; -namespace MethodRedirect +namespace Scenarios_UT { class Scenario6 { @@ -11,12 +11,12 @@ static void Main(string[] args) public virtual string PublicVirtualInstanceMethod() { - return "MethodRedirect.Scenario6.PublicVirtualInstanceMethod"; + return "Scenarios_UT.Scenario6.PublicVirtualInstanceMethod"; } public virtual string PublicVirtualInstanceMethodWithParameter(int x) { - return "MethodRedirect.Scenario6.PublicVirtualInstanceMethodWithParameter." + x; + return "Scenarios_UT.Scenario6.PublicVirtualInstanceMethodWithParameter." + x; } public virtual int AnotherPublicInstanceMethodWithParameter(int x) @@ -29,7 +29,7 @@ class Scenario6Ext { private string PrivateInstanceMethodWithParameter(int x) { - return "MethodRedirect.Scenario6Ext.PrivateInstanceMethodWithParameter." + x; + return "Scenarios_UT.Scenario6Ext.PrivateInstanceMethodWithParameter." + x; } } } \ No newline at end of file diff --git a/UnitTests/Scenario6_UnitTests.cs b/UnitTests/Scenario6_UnitTests.cs index 582bfcf..3c20887 100644 --- a/UnitTests/Scenario6_UnitTests.cs +++ b/UnitTests/Scenario6_UnitTests.cs @@ -12,42 +12,39 @@ public class Scenario6_UnitTests [TestMethod] public void Redirect_PublicVirtualInstanceMethodWithParameter_To_PrivateInstanceMethodWithParameter_DifferentInstance() { - Assembly assembly = Assembly.GetAssembly(typeof(Scenario6)); - Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario6"); - Type ScenarioExt_Type = assembly.GetType("MethodRedirect.Scenario6Ext"); + Type Scenario_Type = typeof(Scenario6); - MethodInfo Scenario_PublicVirtualInstanceMethodWithParameter = Scenario_Type.GetMethod("PublicVirtualInstanceMethodWithParameter", BindingFlags.Instance | BindingFlags.Public); - MethodInfo ScenarioExt_PrivateInstanceMethodWithParameter = ScenarioExt_Type.GetMethod("PrivateInstanceMethodWithParameter", BindingFlags.Instance | BindingFlags.NonPublic); - - var token = Scenario_PublicVirtualInstanceMethodWithParameter.RedirectTo(ScenarioExt_PrivateInstanceMethodWithParameter); + var token = MethodUtil.HookMethod( + ClassMemberInfo.FromMethod(Scenario_Type, "PublicVirtualInstanceMethodWithParameter"), + ClassMemberInfo.FromMethod(typeof(Scenario6Ext), "PrivateInstanceMethodWithParameter") + ); var scenario = (Scenario6)Activator.CreateInstance(Scenario_Type); int parameter = 123; string methodName = scenario.PublicVirtualInstanceMethodWithParameter(parameter); - Assert.IsTrue(methodName == "MethodRedirect.Scenario6Ext.PrivateInstanceMethodWithParameter." + parameter); + Assert.IsTrue(methodName == "Scenarios_UT.Scenario6Ext.PrivateInstanceMethodWithParameter." + parameter); token.Restore(); methodName = scenario.PublicVirtualInstanceMethodWithParameter(parameter); - Assert.IsTrue(methodName == "MethodRedirect.Scenario6.PublicVirtualInstanceMethodWithParameter." + parameter); + Assert.IsTrue(methodName == "Scenarios_UT.Scenario6.PublicVirtualInstanceMethodWithParameter." + parameter); } [TestMethod] public void Redirect_PublicVirtualInstanceMethod_To_Lambda_NoParameter() { - Assembly assembly = Assembly.GetAssembly(typeof(Scenario6)); - Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario6"); - - MethodInfo Scenario_PublicVirtualInstanceMethod = Scenario_Type.GetMethod("PublicVirtualInstanceMethod", BindingFlags.Instance | BindingFlags.Public); + Type Scenario_Type = typeof(Scenario6); - // Test redirection to lambda expression (no parameter) - var token = Scenario_PublicVirtualInstanceMethod.RedirectTo(() => - { - return "MethodRedirect.LambdaExpression.NoParameter"; - }); + var token = MethodUtil.HookMethod( + ClassMemberInfo.FromMethod(Scenario_Type, "PublicVirtualInstanceMethod"), + ClassMemberInfo.FromFunc(() => + { + return "MethodRedirect.LambdaExpression.NoParameter"; + }) + ); var scenario = (Scenario6)Activator.CreateInstance(Scenario_Type); @@ -59,22 +56,21 @@ public void Redirect_PublicVirtualInstanceMethod_To_Lambda_NoParameter() methodName = scenario.PublicVirtualInstanceMethod(); - Assert.IsTrue(methodName == "MethodRedirect.Scenario6.PublicVirtualInstanceMethod"); - } - + Assert.IsTrue(methodName == "Scenarios_UT.Scenario6.PublicVirtualInstanceMethod"); + } + [TestMethod] public void Redirect_PublicVirtualInstanceMethod_To_Lambda_WithParameter() { - Assembly assembly = Assembly.GetAssembly(typeof(Scenario6)); - Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario6"); + Type Scenario_Type = typeof(Scenario6); - MethodInfo Scenario_PublicVirtualInstanceMethodWithParameter = Scenario_Type.GetMethod("PublicVirtualInstanceMethodWithParameter", BindingFlags.Instance | BindingFlags.Public); - - // Test redirection to lambda expression with parameter (must use explicit type for parameter) - var token = Scenario_PublicVirtualInstanceMethodWithParameter.RedirectTo((int x) => - { - return "MethodRedirect.LambdaExpression.WithParameter." + x; - }); + var token = MethodUtil.HookMethod( + ClassMemberInfo.FromMethod(Scenario_Type, "PublicVirtualInstanceMethodWithParameter"), + ClassMemberInfo.FromFunc((int x) => + { + return "MethodRedirect.LambdaExpression.WithParameter." + x; + }) + ); var scenario = (Scenario6)Activator.CreateInstance(Scenario_Type); @@ -87,23 +83,22 @@ public void Redirect_PublicVirtualInstanceMethod_To_Lambda_WithParameter() methodName = scenario.PublicVirtualInstanceMethodWithParameter(parameter); - Assert.IsTrue(methodName == "MethodRedirect.Scenario6.PublicVirtualInstanceMethodWithParameter." + parameter); + Assert.IsTrue(methodName == "Scenarios_UT.Scenario6.PublicVirtualInstanceMethodWithParameter." + parameter); } [TestMethod] public void Redirect_AnotherPublicInstanceMethod_To_Lambda_WithParameter() { - Assembly assembly = Assembly.GetAssembly(typeof(Scenario6)); - Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario6"); - - MethodInfo Scenario_AnotherPublicInstanceMethodWithParameter = Scenario_Type.GetMethod("AnotherPublicInstanceMethodWithParameter", BindingFlags.Instance | BindingFlags.Public); - - // Test redirection to lambda expression with parameter and integer return value (must use explicit type for parameter) - var token = Scenario_AnotherPublicInstanceMethodWithParameter.RedirectTo((int x) => - { - Debug.WriteLine("Lambda Expression Parameter = " + x.ToString()); - return x + 10; - }); + Type Scenario_Type = typeof(Scenario6); + + var token = MethodUtil.HookMethod( + ClassMemberInfo.FromMethod(Scenario_Type, "AnotherPublicInstanceMethodWithParameter"), + ClassMemberInfo.FromFunc((int x) => + { + Debug.WriteLine("Lambda Expression Parameter = " + x.ToString()); + return x + 10; + }) + ); var scenario = (Scenario6)Activator.CreateInstance(Scenario_Type); diff --git a/UnitTests/Scenario7.cs b/UnitTests/Scenario7.cs new file mode 100644 index 0000000..6405dab --- /dev/null +++ b/UnitTests/Scenario7.cs @@ -0,0 +1,85 @@ +using MethodRedirect; + +namespace Scenarios_UT +{ + public class Scenario7 + { + // Hook will not work without 'public virtual' + public virtual int OverloadMethod(int a1, int a2) + { + return a1 + a2; + } + public virtual string OverloadMethod(string a1, string a2) + { + return a1 + a2; + } + + public virtual void MethodWithOutParam(out string ret) + { + ret = nameof(MethodWithOutParam); + } + + public int delta2add = 5; + + public virtual int M1(int a1, int a2) + { + return a1 + a2 + delta2add; + } + + public virtual int Sum(int a1, int a2) + { + return a1 + a2; + } + public virtual string Sum(string s1, string s2) + { + return s1 + s2; + } + + public virtual int Sub(int a1, int a2) + { + return a1 - a2; + } + + } + + class Scenario7Ext + { + // can be only static, as M1 does not know context of Scenario7Ext class, as it can follow only original class (Scenario7) + public static OriginalMethodsInfo token = null; + + static int M1(Scenario7 pthis, int a1, int a2) + { + // if you call pthis.M1 without token.RestoreOriginal() - you will get stack overflow as function just tries to + // call itself infinitely. + using (token.RestoreOriginal()) + { + pthis.delta2add = 0; // Have access to original class being hooked. + int r = pthis.M1(a1, a2) + 10; + pthis.delta2add = 5; + return r; + } + } + + public virtual int Sum(int a1, int a2) + { + return 0; + } + + // can use object instead of original class in case if class is non-public. + public static string Sum(object scenario7, string s1, string s2) + { + return ""; + } + + public virtual int Sub(int a1, int a2) + { + return 0; + } + public virtual string Sub(string s1, string s2) + { + return ""; + } + + } + +} diff --git a/UnitTests/Scenario7_UnitTests.cs b/UnitTests/Scenario7_UnitTests.cs new file mode 100644 index 0000000..8875b14 --- /dev/null +++ b/UnitTests/Scenario7_UnitTests.cs @@ -0,0 +1,87 @@ +using MethodRedirect; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; + +namespace Scenarios_UT +{ + [TestClass] + public class Scenario7_UnitTests + { + [TestMethod] + public void HookOverloadedMethods() + { + // You won't be able to intercept OverloadMethod with older API, as there are multiple same named methods. + var token = MethodUtil.HookMethod(typeof(Scenario7), "OverloadMethod", + new Func( + (int arg1, int arg2) => + { + return arg1 - arg2; + } + ) + ); + Scenario7 s7 = new Scenario7(); + Assert.AreEqual(s7.OverloadMethod(2, 1), 1); + token.Dispose(); + Assert.AreEqual(s7.OverloadMethod(2, 1), 3); + } + + delegate void DelegateMethodWithOutParam(out string ret); + + [TestMethod] + public void HookOutMethod() + { + // out and ref parameters can be supported only via delegates. + DelegateMethodWithOutParam func = (out string s) => + { + s = nameof(HookOutMethod); + }; + + // You won't be able to intercept OverloadMethod with older API, as there are multiple same named methods. + var token = MethodUtil.HookMethod(typeof(Scenario7), "MethodWithOutParam", func); + Scenario7 s7 = new Scenario7(); + string ret = ""; + s7.MethodWithOutParam(out ret); + Assert.AreEqual(ret, nameof(HookOutMethod)); + token.Dispose(); + s7.MethodWithOutParam(out ret); + Assert.AreEqual(ret, nameof(Scenario7.MethodWithOutParam)); + } + + [TestMethod] + public void CanCallOriginalMethod() + { + Scenario7 s7 = new Scenario7(); + + Scenario7Ext.token = MethodUtil.HookMethod( + ClassMemberInfo.FromMethod(typeof(Scenario7), "M1"), + ClassMemberInfo.FromMethod(typeof(Scenario7Ext), "M1", true) + ); + + Assert.AreEqual(s7.M1(1,2), 13); + } + + [TestMethod] + public void HookMultipleMethods() + { + // Can hook multiple methods with same name, only arguments overload. + var token = MethodUtil.HookMethod(typeof(Scenario7), typeof(Scenario7Ext), "Sum"); + Scenario7 s7 = new Scenario7(); + Assert.AreEqual(s7.Sum(1,2), 0); + Assert.AreEqual(s7.Sum("1","2"), ""); + token.Dispose(); + Assert.AreEqual(s7.Sum(1, 2), 3); + Assert.AreEqual(s7.Sum("1", "2"), "12"); + + // Hook type must have a complete list of methods that must be hooked. + // If method not found in original type, exception will be thrown. + var ex = Assert.ThrowsException(() => { + MethodUtil.HookMethod(typeof(Scenario7), typeof(Scenario7Ext), "Sub"); + }, "yep"); + + Assert.AreEqual(ex.Message, "Hook method Sub(System.String,System.String) does not have mapping in original type"); + } + } + +} +