Динамически заменить содержимое метода C #?

То, что я хочу сделать, это изменить способ выполнения C #-метода при его вызове, чтобы я мог написать что-то вроде этого:

[Distributed] public DTask Solve(int n, DEvent callback) { for (int m = 2; m < n - 1; m += 1) if (m % n == 0) return false; return true; } 

Во время выполнения я должен иметь возможность анализировать методы, которые имеют атрибут Distributed (который я уже могу сделать), а затем вставлять код до того, как тело функции выполнится и после возвращения функции. Что еще более важно, мне нужно иметь возможность сделать это без изменения кода, где вызывается Solve или в начале функции (во время компиляции, это делается во время выполнения – это цель).

На данный момент я попытался выполнить этот бит кода (предположим, что t – это тип, в котором хранится Solve, а m – MethodInfo of Solve) :

 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 ReplacedSolve(int n, DEvent callback) { Console.WriteLine("This was executed instead!"); return true; } 

Однако MethodRental.SwapMethodBody работает только с динамическими модулями; не те, которые уже были скомпилированы и сохранены в сборке.

Поэтому я ищу способ эффективно выполнить SwapMethodBody по методу, который уже хранится в загруженной и исполняющей сборке .

Обратите внимание, что это не проблема, если мне нужно полностью скопировать метод в динамический модуль, но в этом случае мне нужно найти способ копирования через IL, а также обновить все вызовы Solve (), чтобы они будет указывать на новую копию.

Подумайте о последствиях, если это возможно. Вы могли бы, например, заменить содержимое classа String и нанести havock. Когда метод загружается 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"); } } } 

Harmony – это библиотека с открытым исходным кодом, предназначенная для замены, декорирования или модификации существующих методов C # любого типа во время выполнения. Основное внимание уделяется играм и плагинам, написанным в Mono, но этот метод можно использовать с любой версией .NET. Он также заботится о нескольких изменениях того же метода (они накапливаются вместо перезаписи).

Он создает методы типа DynamicMethod для каждого исходного метода и испускает для него код, который вызывает пользовательские методы в начале и в конце. Он также позволяет вам писать фильтры для обработки исходного кода IL, который позволяет более детально манипулировать исходным методом.

Чтобы завершить процесс, он записывает простой ассемблер в батуте исходного метода, который указывает на сборщик, сгенерированный при компиляции динамического метода. Это работает для 32 / 64Bit для Windows, MacOS и любого Linux, поддерживаемого Mono.

Вы можете изменить содержимое метода во время выполнения. Но вы не должны этого делать, и настоятельно рекомендуется сохранить это в целях тестирования.

Просто взгляните на:

http://www.codeproject.com/Articles/463508/NET-CLR-Injection-Modify-IL-Code-during-Run-time

В принципе, вы можете:

  1. Получить контент IL-метода через MethodInfo.GetMethodBody (). GetILAsByteArray ()
  2. Беспокойство с этими байтами.

    Если вы просто хотите добавить или добавить какой-то код, просто представьте / добавьте коды операций, которые вы хотите (будьте осторожны, если вы оставите стек чистым)

    Вот несколько советов, чтобы «уничтожить» существующие ИЛ:

    • Возвращенные байты – это последовательность инструкций IL, за которыми следуют их аргументы (если у них есть некоторые – например, «.call» имеет один аргумент: токен вызываемого метода и «.pop» не имеет)
    • Соответствие между IL-кодами и байтами, которые вы найдете в возвращаемом массиве, может быть найдено с помощью OpCodes.YourOpCode.Value (это реальное значение байта кода операции, сохраненное в вашей сборке)
    • Аргументы, добавленные после IL-кодов, могут иметь разные размеры (от одного до нескольких байтов), в зависимости от кода операции
    • Вы можете найти маркеры, на которые ссылаются аргументы, используя соответствующие методы. Например, если ваш IL содержит «.call 354354» (закодирован как 28 00 05 68 32 в шестнадцатеричном формате, 28h = 40 является кодом операции «.call» и 56832h = 354354), соответствующий вызываемый метод можно найти, используя MethodBase.GetMethodFromHandle (354354) )
  3. После изменения вы можете массировать байтовый массив IL через InjectionHelper.UpdateILCodes (метод MethodInfo, byte [] ilCodes) – см. Ссылку, упомянутую выше

    Это «небезопасная» часть … Это хорошо работает, но это связано с взломом внутренних механизмов CLR …

вы можете заменить его, если метод не является виртуальным, не общим, а не общим типом, а не встроенным и на табличке x86:

 MethodInfo methodToReplace = ... RuntimeHelpers.PrepareMetod(methodToReplace.MethodHandle); var getDynamicHandle = Delegate.CreateDelegate(Metadata>.Type, Metadata.Type.GetMethod("GetMethodDescriptor", BindingFlags.Instance | BindingFlags.NonPublic)) as Func; 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. 

Logman , но с интерфейсом для обкатки тел методов. Кроме того, более простой пример.

 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 { ///  /// Swaps the function pointers for a and b, effectively swapping the method bodies. ///  ///  /// a and b must have same signature ///  /// Method to swap /// Method to swap 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; } } } 

Я знаю, что это не точный ответ на ваш вопрос, но обычный способ сделать это – использовать заводы / прокси-подход.

Сначала мы объявляем базовый тип.

 public class SimpleClass { public virtual DTask Solve(int n, DEvent callback) { for (int m = 2; m < n - 1; m += 1) if (m % n == 0) return false; return true; } } 

Затем мы можем объявить производный тип (назовите его прокси).

 public class DistributedClass { public override DTask Solve(int n, DEvent callback) { CodeToExecuteBefore(); return base.Slove(n, callback); } } // At runtime MyClass myInstance; if (distributed) myInstance = new DistributedClass(); else myInstance = new SimpleClass(); 

Полученный тип также может быть сгенерирован во время выполнения.

 public static class Distributeds { private static readonly ConcurrentDictionary pDistributedTypes = new ConcurrentDictionary(); 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() 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.Solve(...); 

Единственная потеря производительности при построении производного объекта, первый раз довольно медленный, потому что он будет использовать много отражения и отражения. Все остальные времена, это стоимость параллельного поиска таблицы и конструктора. Как сказано, вы можете оптимизировать

 ConcurrentDictionary>. 

Вы можете заменить метод во время выполнения, используя интерфейс ICLRPRofiling .

  1. Вызовите AttachProfiler для присоединения к процессу.
  2. Вызовите SetILFunctionBody, чтобы заменить код метода.

См. Этот блог для получения более подробной информации.

Существует несколько фреймворков, которые позволяют динамически изменять любой метод во время выполнения (они используют интерфейс ICLRProfiling, упомянутый пользователем152949):

  • Приг : бесплатный и открытый источник!
  • Microsoft Fakes : коммерческий, включенный в Visual Studio Premium и Ultimate, но не Community и Professional
  • Telerik JustMock : коммерческая, доступна версия «Lite»
  • Типовой изолятор : коммерческий

Есть также несколько фреймворков, которые издеваются над внутренними компонентами .NET, они, скорее всего, более хрупкие и, вероятно, не могут изменить встроенный код, но, с другой стороны, они полностью автономны и не требуют от вас использования обычная пусковая установка.

  • Гармония : лицензия MIT. Кажется, на самом деле он был успешно использован в нескольких игровых моделях, поддерживает как .NET, так и Mono.
  • Deviare In Process Instrumentation Двигатель : GPLv3 и коммерческий. Поддержка .NET в настоящее время отмечена как экспериментальная, но, с другой стороны, имеет преимущество от коммерческой поддержки.
  • Возможно ли одноуровневое многоязычное развертывание Windows Forms (ILMerge и спутниковые сборки / локализация)?
  • Вызов функции стандартной библиотеки C из asm в Visual Studio
  • Visual Studio «Не удалось скопировать» ... во время сборки
  • Эффективное умножение матрицы 4x4 (C vs assembly)
  • Цикл с вызовом функции быстрее, чем пустой цикл
  • Как сделать kernel ​​для моего загрузчика?
  • Как извлечь сборку из GAC?
  • Что регистрирует сохранение в соглашении вызова ARM C?
  • Как сохранить сборки ASP.NET в AppDomain в живых?
  • Не удалось загрузить файл или сборку HRESULT: 0x80131515 (При добавлении controllerа в проект MVC с ссылками на сборку на сетевом диске)
  • Почему медленная инструкция цикла? Не удалось ли Intel эффективно внедрить его?
  • Давайте будем гением компьютера.