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.
Jawaban:
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
Menambal dengan anotasi Harmoni
Atau, tambalan manual dengan refleksi
sumber
Memory.WriteJump
)?48 B8 <QWord>
bergerak nilai langsung QWORD untukrax
, makaFF E0
adalahjmp rax
- semua jelas ada! Pertanyaan saya yang tersisa adalah tentangE9 <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?Untuk .NET 4 ke atas
sumber
this
di dalamnya,injectionMethod*()
itu akan mereferensikan sebuahInjection
instance selama waktu kompilasi , tetapi sebuahTarget
instance selama runtime (ini berlaku untuk semua referensi ke anggota instance yang Anda gunakan di dalam metode). (2) Untuk beberapa alasan#DEBUG
bagian hanya bekerja ketika debugging ujian, tetapi tidak ketika menjalankan tes yang telah debug-dikompilasi. Saya akhirnya selalu menggunakan#else
bagian itu. Saya tidak mengerti mengapa ini berhasil tetapi berhasil.Debugger.IsAttached
sebagai pengganti#if
preprocessorAnda 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:
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:
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 ...
sumber
Anda dapat menggantinya jika metodenya non virtual, non generik, bukan dalam tipe generik, tidak sebaris dan pada bentuk pelat x86:
sumber
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.
sumber
Solusi Logman , tetapi dengan antarmuka untuk menukar badan metode. Juga, contoh yang lebih sederhana.
sumber
Berdasarkan jawaban atas pertanyaan ini dan pertanyaan lainnya, ive muncul dengan versi rapi ini:
sumber
Anda dapat mengganti metode saat runtime dengan menggunakan ICLRPRofiling Interface .
Lihat blog ini untuk lebih jelasnya.
sumber
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.
Kemudian kita bisa mendeklarasikan tipe turunan (sebut saja proxy).
Jenis turunan juga dapat dibuat saat runtime.
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
sumber