【问题标题】:Dynamically replace the contents of a C# method?动态替换 C# 方法的内容?
【发布时间】:2022-01-01 23:58:42
【问题描述】:

我想做的是改变 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;
}

在运行时,我需要能够分析具有 Distributed 属性的方法(我已经可以这样做),然后在函数体执行之前和函数返回之后插入代码。更重要的是,我需要能够在不修改调用 Solve 的代码或函数开头(在编译时;在运行时这样做是目标)的情况下做到这一点。

目前我已经尝试了这段代码(假设t是Solve存储的类型,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 仅适用于动态模块;不是那些已经编译并存储在程序集中的。

所以我正在寻找一种方法来有效地对 已存储在已加载并正在执行的程序集中的方法执行 SwapMethodBody

注意,如果我必须将方法完全复制到动态模块中,这不是问题,但在这种情况下,我需要找到一种方法来复制整个 IL 以及更新对 Solve() 的所有调用这样他们就会指向新的副本。

【问题讨论】:

  • 无法交换已加载的方法。否则 Spring.Net 不必用代理和接口做奇怪的事情 :-) 阅读这个问题,它与你的问题相切:stackoverflow.com/questions/25803/…(如果你可以拦截它,你可以交换它......如果你不能 1 那么显然你不能 2)。
  • 在这种情况下,有没有办法将方法复制到动态模块中,并更新程序集的其余部分,以便对该方法的调用指向新副本?
  • 老老实实。如果可以轻松完成,那么所有各种 IoC 容器都可能会这样做。他们不这样做-> 99% 它不能完成:-)(没有可怕和无名的黑客)。只有一个希望:他们承诺在 C# 5.0 中进行元编程和异步。我们已经看到了异步......元编程什么都没有......但它可能是它!
  • 你真的没有解释为什么要让自己陷入如此痛苦的境地。
  • 请看下面我的回答。这是完全可能的。在您不拥有的代码上和运行时。我不明白为什么这么多人认为这是不可能的。

标签: c# methods assemblies cil


【解决方案1】:

披露:Harmony 是一个由我(本文的作者)编写和维护的库。

Harmony 2 是一个开源库(MIT 许可),旨在在运行时替换、修饰或修改任何类型的现有 C# 方法。它主要关注用 Mono 或 .NET 编写的游戏和插件。它负责对同一方法进行多次更改 - 它们会累积而不是相互覆盖。

它为每个原始方法创建动态替换方法,并向它们发出代码,在开始和结束时调用自定义方法。它还允许您编写过滤器来处理原始 IL 代码和自定义异常处理程序,从而允许对原始方法进行更详细的操作。

为了完成这个过程,它编写了一个简单的汇编程序跳转到原始方法的蹦床中,指向从编译动态方法生成的汇编程序。这适用于 Windows、macOS 和 Mono 支持的任何 Linux 上的 32/64 位。

文档可以在here找到。

示例

(Source)

原始代码

public class SomeGameClass
{
    private bool isRunning;
    private int counter;

    private int DoSomething()
    {
        if (isRunning)
        {
            counter++;
            return counter * 10;
        }
    }
}

使用 Harmony 注释进行修补

using SomeGame;
using HarmonyLib;

public class MyPatcher
{
    // make sure DoPatching() is called at start either by
    // the mod loader or by your injector

    public static void DoPatching()
    {
        var harmony = new Harmony("com.example.patch");
        harmony.PatchAll();
    }
}

[HarmonyPatch(typeof(SomeGameClass))]
[HarmonyPatch("DoSomething")]
class Patch01
{
    static FieldRef<SomeGameClass,bool> isRunningRef =
        AccessTools.FieldRefAccess<SomeGameClass, bool>("isRunning");

    static bool Prefix(SomeGameClass __instance, ref int ___counter)
    {
        isRunningRef(__instance) = true;
        if (___counter > 100)
            return false;
        ___counter = 0;
        return true;
    }

    static void Postfix(ref int __result)
    {
        __result *= 2;
    }
}

或者,使用反射手动修补

using SomeGame;
using System.Reflection;
using HarmonyLib;

public class MyPatcher
{
    // make sure DoPatching() is called at start either by
    // the mod loader or by your injector

    public static void DoPatching()
    {
        var harmony = new Harmony("com.example.patch");

        var mOriginal = typeof(SomeGameClass).GetMethod("DoSomething", BindingFlags.Instance | BindingFlags.NonPublic);
        var mPrefix = typeof(MyPatcher).GetMethod("MyPrefix", BindingFlags.Static | BindingFlags.Public);
        var mPostfix = typeof(MyPatcher).GetMethod("MyPostfix", BindingFlags.Static | BindingFlags.Public);
        // add null checks here

        harmony.Patch(mOriginal, new HarmonyMethod(mPrefix), new HarmonyMethod(mPostfix));
    }

    public static void MyPrefix()
    {
        // ...
    }

    public static void MyPostfix()
    {
        // ...
    }
}

【讨论】:

  • 看了一下源码,很有意思!您能否解释(此处和/或文档中)用于执行跳转的具体指令如何工作(Memory.WriteJump)?
  • 据我所知,它还不支持 .NET Core 2,AppDomain.CurrentDomain.DefineDynamicAssembly 出现了一些异常
  • 我的一个朋友 0x0ade 确实向我提到,有一个不太成熟的替代方案适用于 .NET Core,即 NuGet 上的 MonoMod.RuntimeDetour。
  • 更新:通过包含对 System.Reflection.Emit 的引用,Harmony 现在可以使用 .NET Core 3 进行编译和测试
  • @rraallvv 要替换方法,您将创建一个前缀,其中包含原始的所有参数加上__instance(如果不是静态)和ref __result,并让它返回 false 以跳过原始.在其中,您使用 __instance 并将结果分配给 __result 然后返回 false。
【解决方案2】:

适用于 .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");
        }
    }

}

【讨论】:

  • 这值得更多的支持。我有一个完全不同的场景,但是这个 sn-p 正是我需要让我朝着正确的方向前进。谢谢。
  • @Logman 很好的答案。但我的问题是:调试模式下发生了什么?是否可以只替换一条指令?例如,如果我想替换无条件跳转的条件跳转? AFAIK 您正在替换已编译的方法,因此确定我们应该替换哪个条件并不容易......
  • @AlexZhukovskiy 如果你喜欢把它贴在堆栈上并发给我链接。我会调查一下,周末后给你答复。机器 周末后我也会调查你的问题。
  • 在使用 MSTest 进行集成测试时,我注意到了两件事:(1) 当您在 injectionMethod*() 中使用 this 时,它将在 编译时引用 Injection 实例,但在 runtime 期间是一个 Target 实例(对于您在注入方法中使用的所有对实例成员的引用都是如此)。 (2) 出于某种原因,#DEBUG 部分仅在 debugging 测试时起作用,但在 running 已调试编译的测试时不起作用。我最终总是使用#else 部分。我不明白为什么这有效,但确实有效。
  • 非常好。是时候打破一切了! @GoodNightNerdPride 使用 Debugger.IsAttached 而不是 #if 预处理器
【解决方案3】:

您可以在运行时修改方法的内容。但您不应该这样做,强烈建议您保留它以用于测试目的。

看看:

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

基本上,您可以:

  1. 通过 MethodInfo.GetMethodBody().GetILAsByteArray() 获取 IL 方法内容
  2. 弄乱这些字节。

    如果您只想添加或附加一些代码,那么只需添加/添加您想要的操作码(不过要小心保持堆栈干净)

    以下是一些“反编译”现有 IL 的技巧:

    • 返回的字节是一系列 IL 指令,后跟它们的参数(如果有的话 - 例如,'.call' 有一个参数:被调用的方法标记,而 '.pop' 没有)
    • 可以使用 OpCodes.YourOpCode.Value 找到您在返回数组中找到的 IL 代码和字节之间的对应关系(这是保存在程序集中的实际操作码字节值)
    • 附加在 IL 代码之后的参数可能有不同的大小(从一个字节到几个字节),具体取决于调用的操作码
    • 您可以通过适当的方法找到这些参数所引用的标记。例如,如果您的 IL 包含“.call 354354”(在 hexa 中编码为 28 00 05 68 32,28h=40 为 '.call' 操作码和 56832h=354354),则可以使用 MethodBase.GetMethodFromHandle(354354 )
  3. 修改后,您的 IL 字节数组可以通过 InjectionHelper.UpdateILCodes(MethodInfo method, byte[] ilCodes) 重新注入 - 请参阅上面提到的链接

    这是“不安全”的部分...它运作良好,但这包括破解内部 CLR 机制...

【讨论】:

  • 只是为了迂腐,354354 (0x00056832) 不是有效的元数据令牌,高位字节应为 0x06 (MethodDef)、0x0A (MemberRef) 或 0x2B (MethodSpec)。此外,元数据令牌应该以 little-endian 字节顺序写入。最后,元数据令牌是特定于模块的,MethodInfo.MetadataToken 将从声明模块返回令牌,如果您想调用未在与您正在修改的方法相同的模块中定义的方法,则它无法使用。
【解决方案4】:

如果方法是非虚拟、非泛型、非泛型、非内联且在 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.

【讨论】:

  • 这看起来非常危险。我真的希望没有人在生产代码中使用它。
  • 这被应用程序性能监控 (APM) 工具使用,也用于生产。
  • 感谢您的回复,我正在开发一个项目来提供这种功能作为面向方面的编程 API。我解决了在 x86 和 x64 上管理虚拟方法和泛型方法的限制。如果您需要更多详细信息,请告诉我。
  • 什么是元数据类?
  • 这个答案是伪代码并且已经过时了。许多方法已不复存在。
【解决方案5】:

根据对这个问题和另一个问题的回答,我想出了这个整理后的版本:

// Note: This method replaces methodToReplace with methodToInject
// Note: methodToInject will still remain pointing to the same location
public static unsafe MethodReplacementState Replace(this MethodInfo methodToReplace, MethodInfo methodToInject)
        {
//#if DEBUG
            RuntimeHelpers.PrepareMethod(methodToReplace.MethodHandle);
            RuntimeHelpers.PrepareMethod(methodToInject.MethodHandle);
//#endif
            MethodReplacementState state;

            IntPtr tar = methodToReplace.MethodHandle.Value;
            if (!methodToReplace.IsVirtual)
                tar += 8;
            else
            {
                var index = (int)(((*(long*)tar) >> 32) & 0xFF);
                var classStart = *(IntPtr*)(methodToReplace.DeclaringType.TypeHandle.Value + (IntPtr.Size == 4 ? 40 : 64));
                tar = classStart + IntPtr.Size * index;
            }
            var inj = methodToInject.MethodHandle.Value + 8;
#if DEBUG
            tar = *(IntPtr*)tar + 1;
            inj = *(IntPtr*)inj + 1;
            state.Location = tar;
            state.OriginalValue = new IntPtr(*(int*)tar);

            *(int*)tar = *(int*)inj + (int)(long)inj - (int)(long)tar;
            return state;

#else
            state.Location = tar;
            state.OriginalValue = *(IntPtr*)tar;
            * (IntPtr*)tar = *(IntPtr*)inj;
            return state;
#endif
        }
    }

    public struct MethodReplacementState : IDisposable
    {
        internal IntPtr Location;
        internal IntPtr OriginalValue;
        public void Dispose()
        {
            this.Restore();
        }

        public unsafe void Restore()
        {
#if DEBUG
            *(int*)Location = (int)OriginalValue;
#else
            *(IntPtr*)Location = OriginalValue;
#endif
        }
    }

【讨论】:

  • 目前这是最好的答案
  • 添加使用示例会有所帮助
  • 太棒了!我刚试过,它可以工作〜0〜但我想知道它是如何工作的。你能告诉我一些关于它的事情吗?让我们找到答案的链接或主题?
  • 不适用于动态生成的 methodToInject。抛出一切,如 AccessViolationException、内部 CLR 错误、SEXException 等
  • 哦,对不起。需要明确的是,当附加调试器时,它不适用于动态生成的 methodToInject
【解决方案6】:

有几个框架允许您在运行时动态更改任何方法(它们使用 user152949 提到的 ICLRProfiling 接口):

还有一些框架可以模拟 .NET 的内部结构,它们可能更脆弱,并且可能无法更改内联代码,但另一方面,它们是完全独立的,不需要您使用自定义启动器。

  • Harmony:麻省理工学院许可。似乎实际上已经在一些游戏模组中成功使用,同时支持 .NET 和 Mono。
  • Deviare In Process Instrumentation Engine:GPLv3 和商业。 .NET 支持目前被标记为实验性的,但另一方面也有商业支持的好处。

【讨论】:

    【解决方案7】:

    Logman's solution,但带有用于交换方法体的接口。还有一个更简单的例子。

    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;
            }
        }
    }
    

    【讨论】:

    • 这给了我:在 MA.ELCalc.FunctionalTests.dll 中发生了“System.AccessViolationException”类型的异常,但未在用户代码中处理其他信息:尝试读取或写入受保护的内存。这通常表明其他内存已损坏。,,,更换吸气剂时。
    • 我收到异常“wapMethodBodies 尚未处理 8 的 IntPtr 大小”
    【解决方案8】:

    您可以使用ICLRPRofiling Interface 在运行时替换方法。

    1. 调用AttachProfiler 附加到进程。
    2. 调用SetILFunctionBody替换方法代码。

    See this blog了解更多详情。

    【讨论】:

      【解决方案9】:

      基于TakeMeAsAGuest's answer,这里有一个类似的扩展,它不需要使用unsafe 块。

      这是Extensions 类:

      using System;
      using System.Reflection;
      using System.Runtime.CompilerServices;
      using System.Runtime.InteropServices;
      
      namespace MethodRedirect
      {
          static class Extensions
          { 
              public static void RedirectTo(this MethodInfo origin, MethodInfo target)
              {
                  IntPtr ori = GetMethodAddress(origin);
                  IntPtr tar = GetMethodAddress(target);
               
                  Marshal.Copy(new IntPtr[] { Marshal.ReadIntPtr(tar) }, 0, ori, 1);
              }
      
              private static IntPtr GetMethodAddress(MethodInfo mi)
              {
                  const ushort SLOT_NUMBER_MASK = 0xfff; // 3 bytes
                  const int MT_OFFSET_32BIT = 0x28;      // 40 bytes
                  const int MT_OFFSET_64BIT = 0x40;      // 64 bytes
      
                  IntPtr address;
      
                  // JIT compilation of the method
                  RuntimeHelpers.PrepareMethod(mi.MethodHandle);
      
                  IntPtr md = mi.MethodHandle.Value;             // MethodDescriptor address
                  IntPtr mt = mi.DeclaringType.TypeHandle.Value; // MethodTable address
      
                  if (mi.IsVirtual)
                  {
                      // The fixed-size portion of the MethodTable structure depends on the process type
                      int offset = IntPtr.Size == 4 ? MT_OFFSET_32BIT : MT_OFFSET_64BIT;
      
                      // First method slot = MethodTable address + fixed-size offset
                      // This is the address of the first method of any type (i.e. ToString)
                      IntPtr ms = Marshal.ReadIntPtr(mt + offset);
      
                      // Get the slot number of the virtual method entry from the MethodDesc data structure
                      // Remark: the slot number is represented on 3 bytes
                      long shift = Marshal.ReadInt64(md) >> 32;
                      int slot = (int)(shift & SLOT_NUMBER_MASK);
                      
                      // Get the virtual method address relative to the first method slot
                      address = ms + (slot * IntPtr.Size);                                
                  }
                  else
                  {
                      // Bypass default MethodDescriptor padding (8 bytes) 
                      // Reach the CodeOrIL field which contains the address of the JIT-compiled code
                      address = md + 8;
                  }
      
                  return address;
              }
          }
      }
      

      下面是一个简单的用法示例:

      using System;
      using System.Reflection;
      
      namespace MethodRedirect
      {
          class Scenario
          {    
            static void Main(string[] args)
            {
                Assembly assembly = Assembly.GetAssembly(typeof(Scenario));
                Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario");
      
                MethodInfo Scenario_InternalInstanceMethod = Scenario_Type.GetMethod("InternalInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic);
                MethodInfo Scenario_PrivateInstanceMethod = Scenario_Type.GetMethod("PrivateInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic);
      
                Scenario_InternalInstanceMethod.RedirectTo(Scenario_PrivateInstanceMethod);
      
                // Using dynamic type to prevent method string caching
                dynamic scenario = (Scenario)Activator.CreateInstance(Scenario_Type);
      
                bool result = scenario.InternalInstanceMethod() == "PrivateInstanceMethod";
      
                Console.WriteLine("\nRedirection {0}", result ? "SUCCESS" : "FAILED");
      
                Console.ReadKey();
            }
      
            internal string InternalInstanceMethod()
            {
                return "InternalInstanceMethod";
            }
      
            private string PrivateInstanceMethod()
            {
                return "PrivateInstanceMethod";
            }
          }
      }
      

      这是从我在 Github (MethodRedirect) 上提供的一个更详细的项目中提炼出来的。

      【讨论】:

        【解决方案10】:

        我知道这不是您问题的确切答案,但通常的方法是使用工厂/代理方法。

        首先我们声明一个基类型。

        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;
            }
        }
        

        然后我们可以声明一个派生类型(称为代理)。

        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();
        

        派生类型也可以在运行时生成。

        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(...);
        

        唯一的性能损失是在派生对象的构造过程中,第一次很慢,因为它会使用大量的反射和反射发射。 在所有其他时间,这是并发表查找和构造函数的成本。 如前所述,您可以使用优化构造

        ConcurrentDictionary<Type, Func<object>>.
        

        【讨论】:

        • 嗯.. 仍然需要以程序员的名义进行工作以主动了解分布式处理;我正在寻找一种仅依赖于他们在方法上设置 [Distributed] 属性的解决方案(而不是从 ContextBoundObject 继承或继承)。看起来我可能需要使用 Mono.Cecil 或类似的东西对程序集进行一些编译后修改。
        • 我不会说,这是通常的方式。这种方式在所需技能方面很简单(无需了解 CLR),但需要对每个替换的方法/类重复相同的步骤。如果稍后您想更改某些内容(例如,在之后执行某些代码,而不仅仅是之前),那么您将不得不执行 N 次(与需要执行一次的不安全代码相反)。所以这是 N 小时的工作与 1 小时的工作)
        【解决方案11】:

        看看 Mono.Cecil:

        using Mono.Cecil;
        using Mono.Cecil.Inject;
        
        public class Patcher
        {    
           public void Patch()
           {
            // Load the assembly that contains the hook method
            AssemblyDefinition hookAssembly = AssemblyLoader.LoadAssembly("MyHookAssembly.dll");
            // Load the assembly
            AssemblyDefinition targetAssembly = AssemblyLoader.LoadAssembly("TargetAssembly.dll");
        
            // Get the method definition for the injection definition
            MethodDefinition myHook = hookAssembly.MainModule.GetType("HookNamespace.MyHookClass").GetMethod("MyHook");
            // Get the method definition for the injection target. 
            // Note that in this example class Bar is in the global namespace (no namespace), which is why we don't specify the namespace.
            MethodDefinition foo = targetAssembly.MainModule.GetType("Bar").GetMethod("Foo");
        
            // Create the injector
            InjectionDefinition injector = new InjectionDefinition(foo, myHook, InjectFlags.PassInvokingInstance | InjectFlags.passParametersVal);
        
            // Perform the injection with default settings (inject into the beginning before the first instruction)
            injector.Inject();
        
            // More injections or saving the target assembly...
           }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-06-09
          • 1970-01-01
          • 1970-01-01
          • 2012-11-02
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多