dynamicreplaceC#方法的内容?
我想要做的是改变一个C#方法在被调用的时候的执行方式,这样我可以写下如下的东西:
[Distributed] public DTask<bool> Solve(int n, DEvent<bool> callback) { for (int m = 2; m < n - 1; m += 1) if (m % n == 0) return false; return true; }
在运行时,我需要能够分析具有分布式属性(我已经可以做到)的方法,然后在函数正文执行之前和函数返回之前插入代码。 更重要的是,我需要能够在不调整Solve的地方修改代码,或者在函数启动的时候(编译时;在运行时这样做)。
目前我已经尝试了这一小段代码(假设t是Solve存储的types,m是Solve的MethodInfo) :
private void WrapMethod(Type t, MethodInfo m) { // Generate ILasm for delegate. byte[] il = typeof(Dpm).GetMethod("ReplacedSolve").GetMethodBody().GetILAsByteArray(); // Pin the bytes in the garbage collection. GCHandle h = GCHandle.Alloc((object)il, GCHandleType.Pinned); IntPtr addr = h.AddrOfPinnedObject(); int size = il.Length; // Swap the method. MethodRental.SwapMethodBody(t, m.MetadataToken, addr, size, MethodRental.JitImmediate); } public DTask<bool> ReplacedSolve(int n, DEvent<bool> callback) { Console.WriteLine("This was executed instead!"); return true; }
但是,MethodRental.SwapMethodBody只适用于dynamic模块; 而不是那些已经被编译和存储在程序集中的。
所以我正在寻找一种方法来有效地做一个方法已经存储在一个加载和执行大会的 SwapMethodBody。
请注意,如果必须将方法完全复制到dynamic模块中,则不是问题,但在这种情况下,我需要find一种跨IL复制的方法,并将所有调用更新为Solve(),以使它们会指向新的副本。
只要想到这是可能的影响。 你可以例如replaceString
类的内容,并摧毁琐事。 一旦方法被CLR加载,它就不能被修改。 您可以看看AOP和Castle DynamicProxy等库,嘲笑框架(如Rhino Mocks)使用。
对于.NET 4及以上版本
using System; using System.Reflection; using System.Runtime.CompilerServices; namespace InjectionTest { class Program { static void Main(string[] args) { Target targetInstance = new Target(); targetInstance.test(); Injection.install(1); Injection.install(2); Injection.install(3); Injection.install(4); targetInstance.test(); Console.Read(); } } public class Target { public void test() { targetMethod1(); Console.WriteLine(targetMethod2()); targetMethod3("Test"); targetMethod4(); } private void targetMethod1() { Console.WriteLine("Target.targetMethod1()"); } private string targetMethod2() { Console.WriteLine("Target.targetMethod2()"); return "Not injected 2"; } public void targetMethod3(string text) { Console.WriteLine("Target.targetMethod3("+text+")"); } private void targetMethod4() { Console.WriteLine("Target.targetMethod4()"); } } public class Injection { public static void install(int funcNum) { MethodInfo methodToReplace = typeof(Target).GetMethod("targetMethod"+ funcNum, BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); MethodInfo methodToInject = typeof(Injection).GetMethod("injectionMethod"+ funcNum, BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); RuntimeHelpers.PrepareMethod(methodToReplace.MethodHandle); RuntimeHelpers.PrepareMethod(methodToInject.MethodHandle); unsafe { if (IntPtr.Size == 4) { int* inj = (int*)methodToInject.MethodHandle.Value.ToPointer() + 2; int* tar = (int*)methodToReplace.MethodHandle.Value.ToPointer() + 2; #if DEBUG Console.WriteLine("\nVersion x86 Debug\n"); byte* injInst = (byte*)*inj; byte* tarInst = (byte*)*tar; int* injSrc = (int*)(injInst + 1); int* tarSrc = (int*)(tarInst + 1); *tarSrc = (((int)injInst + 5) + *injSrc) - ((int)tarInst + 5); #else Console.WriteLine("\nVersion x86 Release\n"); *tar = *inj; #endif } else { long* inj = (long*)methodToInject.MethodHandle.Value.ToPointer()+1; long* tar = (long*)methodToReplace.MethodHandle.Value.ToPointer()+1; #if DEBUG Console.WriteLine("\nVersion x64 Debug\n"); byte* injInst = (byte*)*inj; byte* tarInst = (byte*)*tar; int* injSrc = (int*)(injInst + 1); int* tarSrc = (int*)(tarInst + 1); *tarSrc = (((int)injInst + 5) + *injSrc) - ((int)tarInst + 5); #else Console.WriteLine("\nVersion x64 Release\n"); *tar = *inj; #endif } } } private void injectionMethod1() { Console.WriteLine("Injection.injectionMethod1"); } private string injectionMethod2() { Console.WriteLine("Injection.injectionMethod2"); return "Injected 2"; } private void injectionMethod3(string text) { Console.WriteLine("Injection.injectionMethod3 " + text); } private void injectionMethod4() { System.Diagnostics.Process.Start("calc"); } } }
您可以在运行时修改方法的内容。 但是你不应该这么做,而且强烈build议为了testing目的而保留这些。
只要看看:
http://www.codeproject.com/Articles/463508/NET-CLR-Injection-Modify-IL-Code-during-Run-time
基本上,你可以:
- 通过MethodInfo.GetMethodBody()获取IL方法内容GetILAsByteArray()
-
与这些字节混淆。
如果你只是想预先或附加一些代码,然后只是预先/附加你想要的操作码(请注意保持堆栈清理,虽然)
这里有一些提示“不能编译”现有的IL:
- 返回的字节是一系列的IL指令,后面是它们的参数(如果它们有一些 – 例如'.call'有一个参数:被调用的方法标记,'.pop'没有)
- IL代码和在返回数组中find的字节之间的对应关系可以使用OpCodes.YourOpCode.Value(它是保存在程序集中的实际操作码字节值)
- 在IL代码之后附加的参数可能有不同的大小(从一个到几个字节),这取决于调用的操作码
- 你可能会发现这些论点是通过适当的方法提到的。 例如,如果您的IL包含“.call 354354”(编码为28 00 05 68 32六进制,28h = 40为'.call'操作码和56832h = 354354),相应的调用方法可以使用MethodBase.GetMethodFromHandle(354354 )
-
一旦被修改,你的IL字节数组可以通过InjectionHelper.UpdateILCodes(MethodInfo方法,byte [] ilCodes)重新注入 – 见上面提到的链接
这是“不安全”的部分…它运作良好,但是这包括黑客内部的CLR机制。
Harmony是一个开源的库,用于在运行时replace,装饰或修改任何types的现有C#方法。 它的主要重点是在单声道编写的游戏和插件,但技术可以用于任何.NET版本。 它也考虑到对同一方法的多重改变(它们积累而不是覆盖)。
它为每个原始方法创build一个DynamicMethodtypes的方法,并向其发出调用自定义方法的代码。 它还允许您编写filter来处理原始IL代码,从而允许对原始方法进行更详细的操作。
为了完成这个过程,它将一个简单的汇编器跳转到原始方法的蹦床上,该方法指向编译dynamic方法时生成的汇编程序。 这适用于Windows,MacOS以及Mono支持的任何Linux上的32/64位。
你可以replace它,如果方法是非虚拟的,非generics的,不是genericstypes,不内联和x86平台:
MethodInfo methodToReplace = ... RuntimeHelpers.PrepareMetod(methodToReplace.MethodHandle); var getDynamicHandle = Delegate.CreateDelegate(Metadata<Func<DynamicMethod, RuntimeMethodHandle>>.Type, Metadata<DynamicMethod>.Type.GetMethod("GetMethodDescriptor", BindingFlags.Instance | BindingFlags.NonPublic)) as Func<DynamicMethod, RuntimeMethodHandle>; var newMethod = new DynamicMethod(...); var body = newMethod.GetILGenerator(); body.Emit(...) // do what you want. body.Emit(OpCodes.jmp, methodToReplace); body.Emit(OpCodes.ret); var handle = getDynamicHandle(newMethod); RuntimeHelpers.PrepareMethod(handle); *((int*)new IntPtr(((int*)methodToReplace.MethodHandle.Value.ToPointer() + 2)).ToPointer()) = handle.GetFunctionPointer().ToInt32(); //all call on methodToReplace redirect to newMethod and methodToReplace is called in newMethod and you can continue to debug it, enjoy.
我知道这不是你的问题的确切答案,但通常的做法是使用工厂/代理方法。
首先我们声明一个基types。
public class SimpleClass { public virtual DTask<bool> Solve(int n, DEvent<bool> callback) { for (int m = 2; m < n - 1; m += 1) if (m % n == 0) return false; return true; } }
然后我们可以声明一个派生types(称之为代理)。
public class DistributedClass { public override DTask<bool> Solve(int n, DEvent<bool> callback) { CodeToExecuteBefore(); return base.Slove(n, callback); } } // At runtime MyClass myInstance; if (distributed) myInstance = new DistributedClass(); else myInstance = new SimpleClass();
派生types也可以在运行时生成。
public static class Distributeds { private static readonly ConcurrentDictionary<Type, Type> pDistributedTypes = new ConcurrentDictionary<Type, Type>(); public Type MakeDistributedType(Type type) { Type result; if (!pDistributedTypes.TryGetValue(type, out result)) { if (there is at least one method that have [Distributed] attribute) { result = create a new dynamic type that inherits the specified type; } else { result = type; } pDistributedTypes[type] = result; } return result; } public T MakeDistributedInstance<T>() where T : class { Type type = MakeDistributedType(typeof(T)); if (type != null) { // Instead of activator you can also register a constructor delegate generated at runtime if performances are important. return Activator.CreateInstance(type); } return null; } } // In your code... MyClass myclass = Distributeds.MakeDistributedInstance<MyClass>(); myclass.Solve(...);
唯一的性能损失是在构build派生对象的过程中,第一次很慢,因为它会使用大量的reflection和reflection。 所有其他时间,这是一个并发查表和一个构造函数的代价。 如上所述,您可以使用优化施工
ConcurrentDictionary<Type, Func<object>>.
Logman的解决scheme ,但有一个交换方法体的接口。 另外,一个更简单的例子。
using System; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; namespace DynamicMojo { class Program { static void Main(string[] args) { Animal kitty = new HouseCat(); Animal lion = new Lion(); var meow = typeof(HouseCat).GetMethod("Meow", BindingFlags.Instance | BindingFlags.NonPublic); var roar = typeof(Lion).GetMethod("Roar", BindingFlags.Instance | BindingFlags.NonPublic); Console.WriteLine("<==(Normal Run)==>"); kitty.MakeNoise(); //HouseCat: Meow. lion.MakeNoise(); //Lion: Roar! Console.WriteLine("<==(Dynamic Mojo!)==>"); DynamicMojo.SwapMethodBodies(meow, roar); kitty.MakeNoise(); //HouseCat: Roar! lion.MakeNoise(); //Lion: Meow. Console.WriteLine("<==(Normality Restored)==>"); DynamicMojo.SwapMethodBodies(meow, roar); kitty.MakeNoise(); //HouseCat: Meow. lion.MakeNoise(); //Lion: Roar! Console.Read(); } } public abstract class Animal { public void MakeNoise() => Console.WriteLine($"{this.GetType().Name}: {GetSound()}"); protected abstract string GetSound(); } public sealed class HouseCat : Animal { protected override string GetSound() => Meow(); private string Meow() => "Meow."; } public sealed class Lion : Animal { protected override string GetSound() => Roar(); private string Roar() => "Roar!"; } public static class DynamicMojo { /// <summary> /// Swaps the function pointers for a and b, effectively swapping the method bodies. /// </summary> /// <exception cref="ArgumentException"> /// a and b must have same signature /// </exception> /// <param name="a">Method to swap</param> /// <param name="b">Method to swap</param> public static void SwapMethodBodies(MethodInfo a, MethodInfo b) { if (!HasSameSignature(a, b)) { throw new ArgumentException("a and b must have have same signature"); } RuntimeHelpers.PrepareMethod(a.MethodHandle); RuntimeHelpers.PrepareMethod(b.MethodHandle); unsafe { if (IntPtr.Size == 4) { int* inj = (int*)b.MethodHandle.Value.ToPointer() + 2; int* tar = (int*)a.MethodHandle.Value.ToPointer() + 2; byte* injInst = (byte*)*inj; byte* tarInst = (byte*)*tar; int* injSrc = (int*)(injInst + 1); int* tarSrc = (int*)(tarInst + 1); int tmp = *tarSrc; *tarSrc = (((int)injInst + 5) + *injSrc) - ((int)tarInst + 5); *injSrc = (((int)tarInst + 5) + tmp) - ((int)injInst + 5); } else { throw new NotImplementedException($"{nameof(SwapMethodBodies)} doesn't yet handle IntPtr size of {IntPtr.Size}"); } } } private static bool HasSameSignature(MethodInfo a, MethodInfo b) { bool sameParams = !a.GetParameters().Any(x => !b.GetParameters().Any(y => x == y)); bool sameReturnType = a.ReturnType == b.ReturnType; return sameParams && sameReturnType; } } }
您可以使用ICLRPRofiling接口在运行时replace一个方法。
- 调用AttachProfiler以附加到进程。
- 调用SetILFunctionBody来replace方法代码。
看到这个博客的更多细节。
有几个框架允许您在运行时dynamic更改任何方法(它们使用user152949提到的ICLRProfiling接口):
- Prig :免费和开源!
- Microsoft Fakes :Commercial,包含在Visual Studio Premium和Ultimate中,但不包含Community和Professional
- Telerik JustMock :商业,一个“精简版”版本是可用的
- Typemock Isolator :商业