Tự động thay thế nội dung của phương thức C #?


108

Điều tôi muốn làm là thay đổi cách một phương thức C # thực thi khi nó được gọi, để tôi có thể viết một cái gì đó như sau:

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

Tại thời điểm chạy, tôi cần có khả năng phân tích các phương thức có thuộc tính Phân tán (mà tôi đã có thể làm) và sau đó chèn mã trước khi phần thân của hàm thực thi và sau khi hàm trả về. Quan trọng hơn, tôi cần có thể làm điều đó mà không cần sửa đổi mã nơi Solve được gọi hoặc khi bắt đầu hàm (tại thời điểm biên dịch; làm như vậy trong thời gian chạy là mục tiêu).

Hiện tại, tôi đã thử bit mã này (giả sử t là kiểu Solve được lưu trữ và m là MethodInfo của 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<bool> ReplacedSolve(int n, DEvent<bool> callback)
{
    Console.WriteLine("This was executed instead!");
    return true;
}

Tuy nhiên, MethodRental.SwapMethodBody chỉ hoạt động trên các mô-đun động; không phải những cái đã được biên dịch và lưu trữ trong assembly.

Vì vậy, tôi đang tìm cách để thực hiện SwapMethodBody một cách hiệu quả trên một phương thức đã được lưu trữ trong một assembly được tải và đang thực thi .

Lưu ý, sẽ không thành vấn đề nếu tôi phải sao chép hoàn toàn phương thức vào mô-đun động, nhưng trong trường hợp này, tôi cần tìm cách sao chép trên IL cũng như cập nhật tất cả các lệnh gọi tới Solve () sao cho chúng sẽ trỏ đến bản sao mới.


3
Không thể hoán đổi các phương thức đã được tải. Nếu không thì Spring.Net sẽ không phải tạo ra những điều kỳ lạ với proxy và giao diện :-) Đọc câu hỏi này, nó liên quan đến vấn đề của bạn: stackoverflow.com/questions/25803/… (nếu bạn có thể chặn nó, bạn có thể tương tự -swap it ... Nếu bạn không thể 1 thì rõ ràng bạn không thể 2).
xanatos

Trong trường hợp đó, có cách nào để sao chép một phương thức vào một mô-đun động và cập nhật phần còn lại của hợp ngữ mà các lệnh gọi phương thức đó trỏ tới bản sao mới không?
Tháng 6 Rhodes

Cùng một cũ, cùng một cũ. Nếu nó có thể được thực hiện dễ dàng, tất cả các container IoC khác nhau có thể sẽ làm được. Họ không làm điều đó-> 99% là không thể thực hiện được :-) (không có các vụ hack khủng khiếp và có thể đổi mới). Có một hy vọng duy nhất: họ hứa sẽ lập trình siêu hình và không đồng bộ hóa trong C # 5.0. Không đồng bộ mà chúng tôi đã thấy ... Không có gì lập trình siêu thị ... NHƯNG nó có thể là nó!
xanatos

1
Bạn thực sự chưa giải thích được lý do tại sao bạn lại muốn thả mình vào trong một điều gì đó quá đau khổ.
DanielOfTaebl

6
Hãy xem câu trả lời của tôi dưới đây. Điều này là hoàn toàn có thể. Trên mã bạn không sở hữu và trong thời gian chạy. Tôi không hiểu tại sao nhiều người nghĩ rằng điều này là không thể.
Andreas Pardeike

Câu trả lời:


201

Tiết lộ: Harmony là một thư viện được viết và được duy trì bởi tôi, tác giả của bài đăng này.

Harmony 2 là một thư viện mã nguồn mở (giấy phép MIT) được thiết kế để thay thế, trang trí hoặc sửa đổi các phương thức C # hiện có dưới bất kỳ hình thức nào trong thời gian chạy. Nó tập trung chính là các trò chơi và plugin được viết bằng Mono hoặc .NET. Nó xử lý nhiều thay đổi đối với cùng một phương pháp - chúng tích lũy thay vì ghi đè lẫn nhau.

Nó tạo ra các phương thức thay thế động cho mọi phương thức ban đầu và phát ra mã cho chúng để gọi các phương thức tùy chỉnh ở đầu và cuối. Nó cũng cho phép bạn viết các bộ lọc để xử lý mã IL gốc và các trình xử lý ngoại lệ tùy chỉnh cho phép thao tác chi tiết hơn với phương pháp gốc.

Để hoàn tất quá trình, nó viết một trình lắp ráp đơn giản nhảy vào tấm bạt lò xo của phương pháp gốc để trỏ đến trình lắp ráp được tạo ra từ việc biên dịch phương thức động. Điều này hoạt động cho 32 / 64Bit trên Windows, macOS và bất kỳ Linux nào mà Mono hỗ trợ.

Tài liệu có thể được tìm thấy ở đây .

Thí dụ

( Nguồn )

Mã gốc

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

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

Chắp vá bằng chú thích 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;
    }
}

Ngoài ra, vá thủ công với phản chiếu

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

        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()
    {
        // ...
    }
}

Đã xem mã nguồn, rất thú vị! Bạn có thể giải thích (tại đây và / hoặc trong tài liệu) cách thức hoạt động của các hướng dẫn cụ thể được sử dụng để thực hiện bước nhảy (vào Memory.WriteJump) không?
Tom

Để trả lời một phần bình luận của riêng tôi: 48 B8 <QWord>di chuyển một QWord giá trị ngay lập tức để rax, sau đó FF E0jmp rax- tất cả rõ ràng đó! Câu hỏi còn lại của tôi là về E9 <DWord>trường hợp (một bước nhảy gần): có vẻ như trong trường hợp này, bước nhảy gần được giữ nguyên và sửa đổi nằm trên mục tiêu của bước nhảy; Khi nào Mono tạo ra mã như vậy ngay từ đầu, và tại sao nó lại được xử lý đặc biệt này?
Tom

1
Theo như tôi có thể nói thì nó chưa hỗ trợ .NET Core 2, nhận được một số ngoại lệ với AppDomain.CurrentDomain.DefineDynamicAssembly
Tối đa

1
Một người bạn của tôi, 0x0ade đã đề cập với tôi rằng có một giải pháp thay thế ít trưởng thành hơn hoạt động trên .NET Core, đó là MonoMod.RuntimeDetour trên NuGet.
Andreas Pardeike

1
Cập nhật: Bằng cách bao gồm một tham chiếu đến System.Reflection.Emit, Harmony hiện biên dịch và kiểm tra OK với .NET Core 3
Andreas Pardeike

181

Đối với .NET 4 trở lên

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

}

14
Điều này xứng đáng nhận được nhiều ủng hộ hơn nữa. Tôi có một kịch bản hoàn toàn khác nhưng đoạn mã này chính là thứ tôi cần để đưa tôi đi đúng hướng. Cảm ơn.
SC

2
@Logman câu trả lời tuyệt vời. Nhưng câu hỏi của tôi là: Điều gì đang xảy ra trong chế độ gỡ lỗi? Và có thể thay thế chỉ một lệnh không? Ví dụ nếu tôi muốn thay thế bước nhảy có điều kiện bằng bước nhảy không điều kiện? AFAIK bạn đang thay thế phương pháp biên soạn, vì vậy nó không phải dễ dàng để xác định điều kiện chúng ta nên thay thế ...
Alex Zhukovskiy

2
@AlexZhukovskiy nếu bạn thích hãy đăng nó trên ngăn xếp và gửi cho tôi liên kết. Tôi sẽ xem xét nó và đưa ra câu trả lời cho bạn sau cuối tuần. Máy Tôi cũng sẽ xem xét câu hỏi của bạn sau cuối tuần.
Logman

2
Hai điều tôi nhận thấy khi thực hiện điều này cho một bài kiểm tra tích hợp với MSTest: (1) Khi bạn sử dụng thisbên trong, injectionMethod*()nó sẽ tham chiếu đến một Injectionthể hiện trong thời gian biên dịch , nhưng một Targetthể hiện trong thời gian chạy (điều này đúng với tất cả các tham chiếu đến các thành viên thể hiện bạn sử dụng bên trong phương pháp). (2) Vì lý do nào đó, #DEBUGphần này chỉ hoạt động khi gỡ lỗi kiểm tra, nhưng không hoạt động khi chạy kiểm tra đã được biên dịch gỡ lỗi. Tôi đã kết thúc luôn luôn sử dụng #elsemột phần. Tôi không hiểu tại sao điều này hoạt động nhưng nó có.
Good Night Nerd Pride

2
rất đẹp. thời gian để phá vỡ mọi thứ! @GoodNightNerdPride sử dụng Debugger.IsAttachedthay vì sử dụng #if bộ tiền xử lý
M.kazem Akhgary

25

Bạn CÓ THỂ sửa đổi nội dung của một phương thức trong thời gian chạy. Nhưng bạn không nên làm như vậy và chúng tôi đặc biệt khuyên bạn nên giữ nó cho mục đích thử nghiệm.

Chỉ cần xem qua:

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

Về cơ bản, bạn có thể:

  1. Nhận nội dung phương thức IL qua MethodInfo.GetMethodBody (). GetILAsByteArray ()
  2. Lộn xộn với những byte này.

    Nếu bạn chỉ muốn thêm trước hoặc nối thêm một số mã, thì chỉ cần viết trước / nối thêm các mã op mà bạn muốn (tuy nhiên, hãy cẩn thận về việc giữ sạch ngăn xếp)

    Dưới đây là một số mẹo để "giải nén" IL hiện có:

    • Các byte được trả về là một chuỗi các lệnh IL, theo sau là các đối số của chúng (nếu chúng có một số - ví dụ: '.call' có một đối số: mã thông báo phương thức được gọi và '.pop' không có)
    • Có thể tìm thấy sự tương ứng giữa mã IL và byte mà bạn tìm thấy trong mảng được trả về bằng cách sử dụng OpCodes.YourOpCode.Value (là giá trị byte opcode thực được lưu trong hợp ngữ của bạn)
    • Các đối số được nối sau mã IL có thể có các kích thước khác nhau (từ một đến vài byte), tùy thuộc vào opcode được gọi
    • Bạn có thể tìm thấy các mã thông báo mà các đối số của luận văn đề cập đến thông qua các phương thức thích hợp. Ví dụ: nếu IL của bạn chứa ".call 354354" (được mã hóa là 28 00 05 68 32 trong hexa, 28h = 40 là opcode '.call' và 56832h = 354354), có thể tìm thấy phương thức được gọi tương ứng bằng MethodBase.GetMethodFromHandle (354354 )
  3. Sau khi sửa đổi, mảng byte IL của bạn có thể được đưa lại thông qua InjectionHelper.UpdateILCodes (phương thức MethodInfo, byte [] ilCodes) - xem liên kết được đề cập ở trên

    Đây là phần "không an toàn" ... Nó hoạt động tốt, nhưng điều này bao gồm việc hack các cơ chế CLR nội bộ ...


7
Nói một cách phức tạp, 354354 (0x00056832) không phải là mã thông báo siêu dữ liệu hợp lệ, byte bậc cao phải là 0x06 (MethodDef), 0x0A (MemberRef) hoặc 0x2B (MethodSpec). Ngoài ra, mã thông báo siêu dữ liệu nên được viết theo thứ tự byte nhỏ. Cuối cùng, mã thông báo siêu dữ liệu là mô-đun cụ thể và MethodInfo.MetadataToken sẽ trả về mã thông báo từ mô-đun khai báo, khiến nó không thể sử dụng được nếu bạn muốn gọi một phương thức không được xác định trong cùng mô-đun với phương thức bạn đang sửa đổi.
Brian Reichle

13

bạn có thể thay thế nó nếu phương thức không phải là ảo, không chung chung, không thuộc kiểu chung, không nội dòng và trên dạng đĩa 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.

Điều đó trông nguy hiểm điên rồ. Tôi thực sự hy vọng không ai sử dụng nó trong mã sản xuất.
Brian Reichle

2
Điều này được sử dụng bởi các công cụ giám sát hiệu suất ứng dụng (APM) và cũng được sử dụng trong sản xuất.
Martin Kersten

1
Cảm ơn bạn đã trả lời, tôi đang làm việc trong một dự án để cung cấp loại khả năng này dưới dạng API lập trình hướng theo phương diện. Tôi đã giải quyết hạn chế của mình để quản lý phương thức ảo và phương thức chung trên cả x86 & x64. Hãy cho tôi biết nếu như bạn cần thêm chị tiết.
Teter 28

6
Siêu dữ liệu lớp là gì?
Sebastian

Câu trả lời này là mã giả và lỗi thời. Nhiều phương pháp không còn tồn tại.
N-ate

9

Có một vài khung công tác cho phép bạn thay đổi động bất kỳ phương thức nào trong thời gian chạy (chúng sử dụng giao diện ICLRProfiling được đề cập bởi user152949):

  • Prig : Mã nguồn mở và miễn phí!
  • Microsoft Fakes : Thương mại, có trong Visual Studio Premium và Ultimate nhưng không phải Community và Professional
  • Telerik JustMock : Thương mại, có sẵn phiên bản "lite"
  • Typemock Isolator : Thương mại

Ngoài ra còn có một số khung làm việc xung quanh với nội bộ của .NET, chúng có thể dễ hỏng hơn và có thể không thể thay đổi mã nội tuyến, nhưng mặt khác, chúng hoàn toàn độc lập và không yêu cầu bạn sử dụng trình khởi chạy tùy chỉnh.

  • Hài hòa : MIT cấp phép. Có vẻ như nó thực sự đã được sử dụng thành công trong một số mod trò chơi, hỗ trợ cả .NET và Mono.
  • Deviare In Process Instrumentation Engine : GPLv3 và Commercial. Hỗ trợ .NET hiện được đánh dấu là thử nghiệm, nhưng mặt khác có lợi ích là được hỗ trợ về mặt thương mại.

8

Giải pháp của Logman , nhưng với một giao diện để hoán đổi thân phương thức. Ngoài ra, một ví dụ đơn giản hơn.

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

1
Điều này đã cho tôi: Một ngoại lệ của loại 'System.AccessViolationException' đã xảy ra trong MA.ELCalc.FuncTests.dll nhưng không được xử lý trong mã người dùng Thông tin bổ sung: Đã cố gắng đọc hoặc ghi bộ nhớ được bảo vệ. Đây thường là một dấu hiệu cho thấy bộ nhớ khác bị hỏng. ,,, khi thay thế một getter.
N-ate

Tôi có ngoại lệ "wapMethodBodies chưa xử lý kích thước IntPtr của 8"
Phong Dao

6

Dựa trên câu trả lời cho câu hỏi này và câu hỏi khác, tôi đã đưa ra phiên bản gọn gàng này:

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

Hiện tại, đây là câu trả lời hay nhất
Eugene Gorbovoy

sẽ rất hữu ích nếu thêm một ví dụ sử dụng
kofifus


3

Tôi biết đó không phải là câu trả lời chính xác cho câu hỏi của bạn, nhưng cách thông thường để làm điều đó là sử dụng cách tiếp cận nhà máy / proxy.

Đầu tiên chúng ta khai báo một kiểu cơ sở.

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

Sau đó, chúng ta có thể khai báo một kiểu dẫn xuất (gọi nó là proxy).

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

Kiểu dẫn xuất cũng có thể được tạo trong thời gian chạy.

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

Sự mất hiệu suất duy nhất là trong quá trình xây dựng đối tượng dẫn xuất, thời gian đầu khá chậm vì nó sẽ sử dụng nhiều phản xạ và phản xạ phát ra. Tất cả những lần khác, nó là chi phí của một tra cứu bảng đồng thời và một hàm tạo. Như đã nói, bạn có thể tối ưu hóa việc xây dựng bằng cách sử dụng

ConcurrentDictionary<Type, Func<object>>.

1
Hmm .. điều đó vẫn đòi hỏi người lập trình phải chủ động nhận thức được quá trình xử lý phân tán; Tôi đang tìm kiếm một giải pháp chỉ dựa vào việc họ đặt thuộc tính [Distributed] trên phương thức (chứ không phải phân lớp hoặc kế thừa từ ContextBoundObject). Có vẻ như tôi có thể cần thực hiện một số sửa đổi sau biên dịch trên các hội đồng bằng Mono.Cecil hoặc một số thứ tương tự.
Tháng 6 Rhodes

Tôi sẽ không nói rằng đây là cách thông thường. Cách này đơn giản về kỹ năng cần thiết (không cần hiểu CLR), nhưng nó yêu cầu lặp lại các bước tương tự cho mỗi phương thức / lớp được thay thế. Nếu sau này bạn muốn thay đổi điều gì đó (ví dụ: thực hiện một số mã sau đó, không chỉ trước đó) thì bạn sẽ phải thực hiện nó N lần (ngược lại với mã không an toàn yêu cầu thực hiện một lần). Vậy đó là công việc N giờ so với công việc 1 giờ)
Eugene Gorbovoy
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.