Secara dinamis mengganti konten metode C #?

109

Yang ingin saya lakukan adalah mengubah cara metode C # dijalankan ketika dipanggil, sehingga saya dapat menulis sesuatu seperti ini:

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

Pada waktu proses, saya harus dapat menganalisis metode yang memiliki atribut Terdistribusi (yang sudah dapat saya lakukan) dan kemudian memasukkan kode sebelum badan fungsi dijalankan dan setelah fungsi kembali. Lebih penting lagi, saya harus dapat melakukannya tanpa memodifikasi kode di mana Solve dipanggil atau di awal fungsi (pada waktu kompilasi; tujuannya adalah melakukannya saat run-time).

Saat ini saya telah mencoba sedikit kode ini (asumsikan t adalah tipe tempat penyimpanan Solve, dan m adalah 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<bool> ReplacedSolve(int n, DEvent<bool> callback)
{
    Console.WriteLine("This was executed instead!");
    return true;
}

Namun, MethodRental.SwapMethodBody hanya bekerja pada modul dinamis; bukan yang sudah dikompilasi dan disimpan di assembly.

Jadi saya mencari cara untuk secara efektif melakukan SwapMethodBody dengan metode yang sudah disimpan dalam rakitan yang dimuat dan dijalankan .

Catatan, ini bukan masalah jika saya harus menyalin sepenuhnya metode ke dalam modul dinamis, tetapi dalam kasus ini saya perlu menemukan cara untuk menyalin di IL serta memperbarui semua panggilan ke Solve () sehingga mereka akan mengarah ke salinan baru.

June Rhodes
sumber
3
Tidak mungkin untuk menukar metode yang sudah dimuat. Jika tidak, Spring.Net tidak perlu membuat hal-hal aneh dengan proxy dan antarmuka :-) Baca pertanyaan ini, ini bersinggungan dengan masalah Anda: stackoverflow.com/questions/25803/… (jika Anda dapat mencegatnya, Anda dapat melakukan sesuatu seperti -tukar ... Jika Anda tidak bisa 1 maka jelas Anda tidak bisa 2).
xanatos
Dalam hal ini, apakah ada cara untuk menyalin metode ke dalam modul dinamis, dan memperbarui sisa rakitan sedemikian rupa sehingga panggilan ke metode tersebut mengarah ke salinan baru?
June Rhodes
Sama tua-sama. Jika itu bisa dilakukan dengan mudah, semua wadah IoC yang beragam mungkin akan melakukannya. Mereka tidak melakukannya-> 99% itu tidak dapat dilakukan :-) (tanpa peretasan yang mengerikan dan tidak dapat dikalahkan). Ada satu harapan: mereka menjanjikan metaprogramming dan async di C # 5.0. Asinkron kita telah melihat ... Metaprogramming apa-apa ... TAPI bisa jadi itu!
xanatos
1
Anda benar-benar belum menjelaskan mengapa Anda ingin membiarkan diri Anda mengalami sesuatu yang begitu menyakitkan.
DanielOfTaebl
6
Silakan lihat jawaban saya di bawah ini. Ini sangat mungkin. Pada kode yang tidak Anda miliki dan selama waktu proses. Saya tidak mengerti mengapa begitu banyak orang berpikir ini tidak mungkin.
Andreas Pardeike

Jawaban:

202

Pengungkapan: Harmoni adalah perpustakaan yang ditulis dan dipelihara oleh saya, penulis posting ini.

Harmony 2 adalah pustaka sumber terbuka (lisensi MIT) yang dirancang untuk menggantikan, menghias, atau memodifikasi metode C # apa pun yang ada selama runtime. Fokus utamanya adalah game dan plugin yang ditulis dalam Mono atau .NET. Ini menangani beberapa perubahan pada metode yang sama - mereka menumpuk, bukan menimpa satu sama lain.

Ini membuat metode penggantian dinamis untuk setiap metode asli dan memancarkan kode yang memanggil metode kustom di awal dan akhir. Ini juga memungkinkan Anda menulis filter untuk memproses kode IL asli dan penangan pengecualian khusus yang memungkinkan manipulasi yang lebih detail dari metode asli.

Untuk menyelesaikan prosesnya, ia menulis lompatan assembler sederhana ke trampolin dari metode asli yang menunjuk ke assembler yang dihasilkan dari kompilasi metode dinamis. Ini berfungsi untuk 32 / 64Bit di Windows, macOS, dan Linux apa pun yang didukung Mono.

Dokumentasi dapat ditemukan di sini .

Contoh

( Sumber )

Kode Asli

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

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

Menambal dengan anotasi Harmoni

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

Atau, tambalan manual dengan refleksi

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()
    {
        // ...
    }
}
Andreas Pardeike
sumber
Sempat melihat-lihat kode sumbernya, sangat menarik! Dapatkah Anda menjelaskan (di sini dan / atau dalam dokumentasi) bagaimana instruksi spesifik bekerja yang digunakan untuk melakukan lompatan (dalam Memory.WriteJump)?
Tom
Untuk menjawab sebagian komentar saya sendiri: 48 B8 <QWord>bergerak nilai langsung QWORD untuk rax, maka FF E0adalah jmp rax- semua jelas ada! Pertanyaan saya yang tersisa adalah tentang E9 <DWord>kasus (lompatan dekat): tampaknya dalam kasus ini lompatan dekat dipertahankan dan modifikasinya pada target lompatan; Kapan Mono menghasilkan kode seperti itu di tempat pertama, dan mengapa mendapat perlakuan khusus ini?
Tom
1
Sejauh yang saya tahu itu belum mendukung .NET Core 2, mendapatkan beberapa pengecualian dengan AppDomain.CurrentDomain.DefineDynamicAssembly
Max
1
Seorang teman saya, 0x0ade pernah menyebutkan kepada saya bahwa ada alternatif yang kurang matang yang berfungsi pada .NET Core, yaitu MonoMod.RuntimeDetour di NuGet.
Andreas Pardeike
1
Pembaruan: Dengan menyertakan referensi ke System.Reflection.Emit, Harmony sekarang mengkompilasi dan menguji OK dengan .NET Core 3
Andreas Pardeike
181

Untuk .NET 4 ke atas

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

}
Tukang tebang kayu
sumber
14
Ini membutuhkan lebih banyak suara positif. Saya memiliki skenario yang sama sekali berbeda tetapi cuplikan ini persis seperti yang saya butuhkan untuk mengarahkan saya ke arah yang benar. Terima kasih.
SC
2
@Log jawaban bagus. Tapi pertanyaan saya adalah: Apa yang terjadi dalam mode debug? Dan apakah mungkin untuk mengganti hanya satu instruksi? Misalnya jika saya ingin mengganti lompatan bersyarat pada yang tidak bersyarat? AFAIK Anda mengganti metode terkompilasi, jadi tidak mudah untuk menentukan kondisi mana yang harus kami ganti ...
Alex Zhukovskiy
2
@AlexZhukovskiy jika Anda suka mempostingnya di tumpukan dan kirimkan saya tautan. Saya akan memeriksanya dan memberi Anda jawaban setelah akhir pekan. Mesin Saya juga akan melihat pertanyaan Anda setelah akhir pekan.
Logman
2
Dua hal yang saya perhatikan ketika melakukan ini untuk uji integrasi dengan MSTest: (1) Ketika Anda menggunakan thisdi dalamnya, injectionMethod*()itu akan mereferensikan sebuah Injectioninstance selama waktu kompilasi , tetapi sebuah Targetinstance selama runtime (ini berlaku untuk semua referensi ke anggota instance yang Anda gunakan di dalam metode). (2) Untuk beberapa alasan #DEBUGbagian hanya bekerja ketika debugging ujian, tetapi tidak ketika menjalankan tes yang telah debug-dikompilasi. Saya akhirnya selalu menggunakan #elsebagian itu. Saya tidak mengerti mengapa ini berhasil tetapi berhasil.
Good Night Nerd Pride
2
sangat bagus. waktunya untuk menghancurkan segalanya! @GoodNightNerdPride digunakan Debugger.IsAttachedsebagai pengganti #if preprocessor
M.kazem Akhgary
25

Anda DAPAT mengubah konten metode saat runtime. Tetapi Anda tidak seharusnya melakukannya, dan sangat disarankan untuk menyimpannya untuk tujuan pengujian.

Lihat saja:

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

Pada dasarnya, Anda dapat:

  1. Dapatkan konten metode IL melalui MethodInfo.GetMethodBody (). GetILAsByteArray ()
  2. Berantakan dengan byte ini.

    Jika Anda hanya ingin menambahkan atau menambahkan beberapa kode, maka cukup praprend / tambahkan opcode yang Anda inginkan (berhati-hatilah dalam membiarkan tumpukan tetap bersih)

    Berikut beberapa tip untuk "mengurai" IL yang ada:

    • Byte yang dikembalikan adalah urutan instruksi IL, diikuti dengan argumennya (jika memiliki beberapa - misalnya, '.call' memiliki satu argumen: token metode yang dipanggil, dan '.pop' tidak memiliki argumen)
    • Korespondensi antara kode IL dan byte yang Anda temukan dalam larik yang dikembalikan dapat ditemukan menggunakan OpCodes.YourOpCode.Value (yang merupakan nilai byte opcode nyata seperti yang disimpan dalam rakitan Anda)
    • Argumen yang ditambahkan setelah kode IL mungkin memiliki ukuran berbeda (dari satu hingga beberapa byte), tergantung pada opcode yang dipanggil
    • Anda mungkin menemukan token yang dirujuk argumen tesis melalui metode yang sesuai. Misalnya, jika IL Anda berisi ".call 354354" (dikodekan sebagai 28 00 05 68 32 dalam hexa, 28h = 40 adalah opcode '.call' dan 56832h = 354354), metode dipanggil yang sesuai dapat ditemukan menggunakan MethodBase.GetMethodFromHandle (354354 )
  3. Setelah diubah, array byte IL Anda dapat diinjeksikan kembali melalui InjectionHelper.UpdateILCodes (metode MethodInfo, byte [] ilCodes) - lihat tautan yang disebutkan di atas

    Ini adalah bagian yang "tidak aman" ... Ini berfungsi dengan baik, tetapi ini terdiri dari peretasan mekanisme CLR internal ...

Olivier
sumber
7
Sekadar sekadar berpandangan luas, 354354 (0x00056832) bukanlah token metadata yang valid, byte orde tinggi harus 0x06 (MethodDef), 0x0A (MemberRef) atau 0x2B (MethodSpec). Selain itu, token metadata harus ditulis dalam urutan byte little-endian. Terakhir, token metadata bersifat khusus modul dan MethodInfo.MetadataToken akan mengembalikan token dari modul deklarasi, membuatnya tidak dapat digunakan jika Anda ingin memanggil metode yang tidak ditentukan dalam modul yang sama dengan metode yang Anda modifikasi.
Brian Reichle
13

Anda dapat menggantinya jika metodenya non virtual, non generik, bukan dalam tipe generik, tidak sebaris dan pada bentuk pelat 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.
Teter28
sumber
Itu terlihat sangat berbahaya. Saya sangat berharap tidak ada yang menggunakannya dalam kode produksi.
Brian Reichle
2
Ini digunakan oleh alat pemantauan kinerja aplikasi (APM) dan digunakan dalam produksi juga.
Martin Kersten
1
Terima kasih atas balasannya, saya sedang mengerjakan proyek untuk menawarkan kemampuan semacam ini sebagai API Pemrograman Berorientasi Aspek. Saya menyelesaikan batasan saya untuk mengelola metode virtual dan metode umum pada x86 & x64. Beri tahu saya jika Anda memerlukan detail lebih lanjut.
Teter28
6
Apa itu Metadata kelas?
Sebastian
Jawaban ini adalah kode palsu dan ketinggalan zaman. Banyak metode sudah tidak ada lagi.
N-makan
9

Terdapat beberapa kerangka kerja yang memungkinkan Anda untuk secara dinamis mengubah metode apa pun pada waktu proses (mereka menggunakan antarmuka ICLRProfiling yang disebutkan oleh user152949):

Ada juga beberapa kerangka kerja yang mengolok-olok dengan internal .NET, ini kemungkinan lebih rapuh, dan mungkin tidak dapat mengubah kode sebaris, tetapi di sisi lain mereka sepenuhnya mandiri dan tidak mengharuskan Anda untuk menggunakan peluncur khusus.

  • Harmony : MIT berlisensi. Tampaknya benar-benar telah berhasil digunakan dalam beberapa mod game, mendukung .NET dan Mono.
  • Deviare Dalam Proses Mesin Instrumentasi : GPLv3 dan Komersial. Dukungan .NET saat ini ditandai sebagai eksperimental, tetapi di sisi lain memiliki keuntungan karena didukung secara komersial.
poizan42
sumber
8

Solusi Logman , tetapi dengan antarmuka untuk menukar badan metode. Juga, contoh yang lebih sederhana.

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;
        }
    }
}
C. McCoy IV
sumber
1
Ini memberi saya: Pengecualian jenis 'System.AccessViolationException' terjadi di MA.ELCalc.FunctionalTests.dll tetapi tidak ditangani dalam kode pengguna Informasi tambahan: Mencoba membaca atau menulis memori yang dilindungi. Ini sering menjadi indikasi bahwa memori lain rusak. ,,, Saat mengganti getter.
N-makan
Saya mendapat pengecualian "wapMethodBodies belum menangani IntPtr ukuran 8"
Phong Dao
6

Berdasarkan jawaban atas pertanyaan ini dan pertanyaan lainnya, ive muncul dengan versi rapi ini:

// 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
        }
    }
TakeMeAsAGuest
sumber
Untuk saat ini, yang ini adalah jawaban terbaik
Eugene Gorbovoy
akan sangat membantu untuk menambahkan contoh penggunaan
kofifus
3

Saya tahu ini bukan jawaban yang tepat untuk pertanyaan Anda, tetapi cara yang biasa dilakukan adalah menggunakan pendekatan pabrik / proxy.

Pertama kami mendeklarasikan tipe dasar.

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

Kemudian kita bisa mendeklarasikan tipe turunan (sebut saja 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();

Jenis turunan juga dapat dibuat saat runtime.

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

Satu-satunya kerugian kinerja adalah selama konstruksi objek turunan, pertama kali cukup lambat karena akan menggunakan banyak pantulan dan pancaran pantulan. Di lain waktu, ini adalah biaya pencarian tabel bersamaan dan konstruktor. Seperti yang dikatakan, Anda dapat mengoptimalkan penggunaan konstruksi

ConcurrentDictionary<Type, Func<object>>.
Salvatore Previti
sumber
1
Hmm .. yang masih membutuhkan pekerjaan atas nama pemrogram untuk secara aktif mengetahui pemrosesan terdistribusi; Saya sedang mencari solusi yang hanya bergantung pada pengaturan atribut [Terdistribusi] pada metode (dan bukan subclass atau mewarisi dari ContextBoundObject). Sepertinya saya mungkin perlu melakukan beberapa modifikasi pasca-kompilasi pada majelis menggunakan Mono.Cecil atau semacamnya.
Juni Rhodes
Saya tidak akan mengatakan, bahwa ini adalah cara yang biasa. Cara ini sederhana dalam hal keterampilan yang dibutuhkan (tidak perlu memahami CLR), tetapi perlu mengulangi langkah yang sama untuk setiap metode / kelas yang diganti. Jika nanti Anda ingin mengubah sesuatu (misalnya, mengeksekusi beberapa kode setelah, tidak hanya sebelumnya) maka Anda harus melakukannya N kali (berbeda dengan kode tidak aman yang mengharuskan melakukannya sekali). Jadi ini adalah pekerjaan N jam vs pekerjaan 1 jam)
Eugene Gorbovoy