Bir C # yönteminin içeriği dinamik olarak değiştirilsin mi?


111

Yapmak istediğim şey, bir C # yönteminin çağrıldığında nasıl çalışacağını değiştirmektir, böylece şöyle bir şey yazabilirim:

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

Çalışma zamanında, Dağıtılmış özniteliğine (zaten yapabildiğim) sahip yöntemleri analiz edebilmem ve ardından işlevin gövdesi çalıştırılmadan önce ve işlev döndükten sonra kod ekleyebilmem gerekiyor. Daha da önemlisi, Solve'nin çağrıldığı yerde veya işlevin başlangıcında (derleme zamanında; bunu çalışma zamanında yapmak amaçtır) kodu değiştirmeden yapabilmem gerekiyor.

Şu anda bu kod parçasını denedim (t'nin Solve'da depolandığı tür olduğunu ve m'nin Çözme'nin MethodInfo olduğunu varsayın) :

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

Ancak, MethodRental.SwapMethodBody yalnızca dinamik modüller üzerinde çalışır; montajda önceden derlenmiş ve saklanmış olanlar değil.

Bu yüzden, SwapMethodBody'yi zaten yüklenmiş ve çalıştırılan bir derlemede depolanan bir yöntemde etkili bir şekilde yapmanın bir yolunu arıyorum .

Yöntemi tamamen dinamik bir modüle kopyalamam gerekip gerekmediğini unutmayın, ancak bu durumda IL genelinde kopyalama yapmanın bir yolunu bulmam ve Solve () 'e yapılan tüm çağrıları, yeni kopyayı gösterir.


3
Zaten yüklenmiş yöntemleri değiştirmek mümkün değil. Aksi takdirde, Spring.Net'in proxy'ler ve arayüzlerle garip şeyler yapması gerekmeyecektir :-) Bu soruyu okuyun, sorununuza teğettir: stackoverflow.com/questions/25803/… (eğer yakalayabilirseniz, benzeri -tavsiye ... Eğer yapamazsan 1 o zaman açıkça yapamazsın 2).
xanatos

Bu durumda, bir yöntemi dinamik bir modüle kopyalamanın ve montajın geri kalanını o yönteme yapılan çağrılar yeni kopyayı gösterecek şekilde güncellemenin bir yolu var mı?
Haziran Rodos

Aynı eski aynı eski. Kolayca yapılabilseydi, tüm çeşitli IoC konteynerleri muhtemelen yapardı. Yapmazlar ->% 99 yapılamaz :-) (korkunç ve akıl almaz hackler olmadan). Tek bir umut var: C # 5.0'da metaprogramlama ve eşzamansızlık sözü verdiler. Async gördük ... Hiçbir şey metaprogramlamıyor ... AMA bu olabilir!
xanatos

1
Neden bu kadar acı verici bir şey için kendini içeri almak istediğini gerçekten açıklamadın.
DanielOfTaebl

6
Lütfen aşağıdaki cevabıma bakın. Bu tamamen mümkündür. Kodda, sahip olmadığınız ve çalışma süresi boyunca. Neden bu kadar çok kişinin bunun mümkün olmadığını düşündüğünü anlamıyorum.
Andreas Pardeike

Yanıtlar:


203

Açıklama: Harmony, bu yazının yazarı olan benim tarafımdan yazılmış ve sürdürülen bir kütüphanedir.

Harmony 2 , çalışma zamanı sırasında her türden mevcut C # yöntemlerini değiştirmek, süslemek veya değiştirmek için tasarlanmış bir açık kaynak kitaplığıdır (MIT lisansı). Ana odak noktası, Mono veya .NET ile yazılmış oyunlar ve eklentilerdir. Aynı yöntemde birden fazla değişiklik yapılmasını sağlar - birbirlerinin üzerine yazmak yerine birikirler.

Her orijinal yöntem için dinamik değiştirme yöntemleri oluşturur ve bunlara başlangıçta ve sonda özel yöntemler çağıran kod yayar. Ayrıca, orijinal IL kodunu işlemek için filtreler yazmanıza ve orijinal yöntemin daha ayrıntılı işlemesine izin veren özel istisna işleyicileri yazmanıza olanak tanır.

Süreci tamamlamak için, dinamik yöntemin derlenmesiyle oluşturulan birleştiriciye işaret eden orijinal yöntemin trambolinine basit bir assembler atlaması yazar. Bu, Windows, macOS ve Mono'nun desteklediği herhangi bir Linux'ta 32 / 64Bit için çalışır.

Belgeler burada bulunabilir .

Misal

( Kaynak )

Orijinal Kod

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

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

Harmony ek açıklamalarıyla yama yapma

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

Alternatif olarak, yansımalı manuel yama

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

Kaynak koduna bir göz attım, çok ilginç! Atlamayı (içeri) gerçekleştirmek için kullanılan özel talimatların nasıl çalıştığını (burada ve / veya belgelerde) açıklayabilir misiniz Memory.WriteJump?
Tom

Kendi 48 B8 <QWord>yorumuma kısmen cevap vermek gerekirse: bir QWord anlık değerini konumuna taşır rax, o zaman FF E0olur jmp rax- orada hepsi açık! Geriye kalan sorum E9 <DWord>durumla ilgili (yakın bir sıçrama): öyle görünüyor ki bu durumda yakın atlama korunmuş ve değişiklik atlamanın hedefi üzerindedir; Mono ilk etapta bu tür bir kodu ne zaman üretir ve neden bu özel muameleyi görür?
Tom

1
Ben AppDomain.CurrentDomain.DefineDynamicAssembly ile bazı istisnalar alma, henüz NET Core 2 desteklemediği söyleyebilir kadarıyla
Max

1
Bir arkadaşım, 0x0ade bana .NET Core üzerinde çalışan daha az olgun bir alternatif, yani NuGet üzerinde MonoMod.RuntimeDetour olduğunu söyledi.
Andreas Pardeike

1
Güncelleme: System.Reflection.Emit'e bir başvuru
ekleyerek

182

.NET 4 ve üstü için

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
Bu çok daha fazla oyu hak ediyor. Tamamen farklı bir senaryom var ama bu pasaj, beni doğru yöne ayarlamak için tam olarak ihtiyacım olan şey. Teşekkürler.
SC

2
@Logman harika cevap. Ama sorum şu: Hata ayıklama modunda neler oluyor? Ve sadece bir talimatı değiştirmek mümkün mü? Örneğin, koşulsuz atlamayı koşulsuz atlamayı değiştirmek istersem? AFAIK, derlenmiş yöntemi değiştiriyorsunuz, bu nedenle hangi durumu değiştirmemiz gerektiğini belirlemek kolay değil ...
Alex Zhukovskiy

2
@AlexZhukovskiy istersen yığına gönder ve bana bağlantı gönder. Hafta sonundan sonra araştırıp size bir cevap vereceğim. Makine hafta sonundan sonra da sorunuza bakacağım.
Logman

2
Bunu MSTest ile bir entegrasyon testi için yaparken fark ettiğim iki şey: (1) thisİçeride kullandığınızda , derleme sırasında injectionMethod*()bir Injectionörneğe başvurur , ancak çalışma zamanı sırasında bir örneğe başvurur (bu, enjekte edilen yöntem). (2) Bazı nedenlerden dolayı, parça yalnızca bir testte hata ayıklanırken çalışıyordu , ancak hata ayıklama ile derlenmiş bir testi çalıştırırken çalışmıyordu . Her zaman parçayı kullanmaya başladım . Bunun neden işe yaradığını anlamıyorum ama işe yarıyor. Target#DEBUG#else
İyi Geceler Nerd Gurur

2
çok hoş. her şeyi kırma zamanı! Önişlemci Debugger.IsAttachedyerine @GoodNightNerdPride kullanın#if
M.kazem Akhgary

25

Bir yöntemin içeriğini çalışma zamanında değiştirebilirsiniz. Ancak bunu yapmamalısınız ve bunu test amacıyla saklamanız şiddetle tavsiye edilir.

Sadece şuna bir göz atın:

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

Temel olarak şunları yapabilirsiniz:

  1. MethodInfo.GetMethodBody () aracılığıyla IL yöntemi içeriğini alın. GetILAsByteArray ()
  2. Bu baytlarla karıştırın.

    Yalnızca bir kodun başına veya sonuna eklemek istiyorsanız, istediğiniz işlem kodlarını önceden ekleyin / ekleyin (yığını temiz bırakmaya dikkat edin)

    Mevcut IL'yi "derlemek" için bazı ipuçları şunlardır:

    • Döndürülen baytlar, IL komutlarının bir dizisidir ve ardından argümanları gelir (eğer bazılarına sahiplerse - örneğin, '.call' bir argümana sahiptir: çağrılan yöntem simgesi ve '.pop' hiçbiri içermez)
    • Döndürülen dizide bulduğunuz IL kodları ve baytlar arasındaki yazışmalar, OpCodes.YourOpCode.Value kullanılarak bulunabilir (bu, derlemenizde kaydedildiği şekliyle gerçek işlem kodu bayt değeridir)
    • IL kodlarından sonra eklenen bağımsız değişkenler, çağrılan işlem koduna bağlı olarak farklı boyutlara (bir ila birkaç bayta kadar) sahip olabilir.
    • Bu argümanların atıfta bulunduğu simgeleri uygun yöntemlerle bulabilirsiniz. Örneğin, IL'niz ".call 354354" içeriyorsa (hexa'da 28 00 05 68 32 kodlu, 28h = 40 '.call' opcode ve 56832h = 354354), karşılık gelen çağrılan yöntem MethodBase.GetMethodFromHandle (354354 )
  3. IL bayt dizisi değiştirildikten sonra, InjectionHelper.UpdateILCodes (MethodInfo yöntemi, bayt [] ilCodes) aracılığıyla yeniden enjekte edilebilir - yukarıda belirtilen bağlantıya bakın

    Bu "güvenli olmayan" kısım ... İyi çalışıyor, ancak bu dahili CLR mekanizmalarını hacklemekten ibaret ...


7
Sadece bilgiçlik göstermek için, 354354 (0x00056832) geçerli bir meta veri belirteci değildir, yüksek sıralı bayt 0x06 (MethodDef), 0x0A (MemberRef) veya 0x2B (MethodSpec) olmalıdır. Ayrıca, meta veri belirteci küçük endian bayt sırasına göre yazılmalıdır. Son olarak, meta veri belirteci modüle özgüdür ve MethodInfo.MetadataToken, belirtme modülünden belirteci döndürür ve değiştirdiğiniz yöntemle aynı modülde tanımlanmamış bir yöntemi çağırmak istiyorsanız onu kullanılamaz hale getirir.
Brian Reichle

13

yöntem sanal değilse, jenerik değilse, jenerik türde değilse, satır içi değilse ve x86 plaka biçimindeyse değiştirebilirsiniz:

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.

Bu çok tehlikeli görünüyor. Umarım kimse bunu üretim kodunda kullanmaz.
Brian Reichle

2
Bu, uygulama performansı izleme (APM) araçları tarafından kullanılır ve üretimde de kullanılır.
Martin Kersten

1
Cevabınız için teşekkürler, Aspect Oriented Programming API olarak bu tür bir yeteneği sunmak için bir proje üzerinde çalışıyorum. Hem x86 hem de x64'te sanal yöntemi ve genel yöntemi yönetme sınırlamamı çözdüm. Daha fazla ayrıntıya ihtiyacınız olursa bana bildirin.
Teter28

6
Sınıf Meta Verileri nedir?
Sebastian

Bu cevap sözde koddur ve güncel değildir. Yöntemlerin çoğu artık mevcut değil.
N-yedi

9

Çalışma zamanında herhangi bir yöntemi dinamik olarak değiştirmenize izin veren birkaç çerçeve vardır (user152949 tarafından belirtilen ICLRProfiling arayüzünü kullanırlar):

Ayrıca .NET'in iç kısımlarıyla dalga geçen birkaç çerçeve vardır, bunlar muhtemelen daha kırılgandır ve muhtemelen satır içi kodu değiştiremezler, ancak diğer yandan tamamen bağımsızdırlar ve bir özel başlatıcı.

  • Harmony : MIT lisanslıdır. Birkaç oyun modunda başarılı bir şekilde kullanılmış gibi görünüyor, hem .NET hem de Mono'yu destekliyor.
  • İşlem Enstrümantasyon Motorunda Sapma : GPLv3 ve Ticari. .NET desteği şu anda deneysel olarak işaretlenmiştir, ancak diğer yandan ticari olarak desteklenme avantajına sahiptir.

8

Logman'ın çözümü , ancak yöntem gövdelerini değiştirmek için bir arayüze sahip. Ayrıca daha basit bir örnek.

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
Bu bana şunu verdi: MA.ELCalc.FunctionalTests.dll'de 'System.AccessViolationException' türünde bir istisna oluştu, ancak kullanıcı kodunda işlenmedi Ek bilgi: Korumalı belleği okuma veya yazma girişiminde bulunuldu. Bu genellikle diğer belleğin bozuk olduğunun bir göstergesidir. ,,, Alıcı değiştirilirken.
N-yedi

"WapMethodBodies henüz IntPtr boyutunu 8 olarak işlemiyor" istisnası var
Phong Dao

7

Bu ve başka bir sorunun cevabına dayanarak, bu düzenli versiyonu buldum:

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

Şu an için bu en iyi cevap
Eugene Gorbovoy

bir kullanım örneği eklemek faydalı olacaktır
kofifus


3

Sorunuzun tam cevabı olmadığını biliyorum, ancak bunu yapmanın olağan yolu fabrikalar / vekil yaklaşımı kullanmaktır.

İlk önce bir temel tür bildiriyoruz.

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

Sonra türetilmiş bir tür tanımlayabiliriz (buna proxy diyebiliriz).

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

Türetilmiş tür, çalışma zamanında da üretilebilir.

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

Tek performans kaybı, türetilen nesnenin yapımı sırasında, ilk kez oldukça yavaştır çünkü çok fazla yansıma ve yansıma yayması kullanacaktır. Diğer tüm zamanlarda, eşzamanlı bir tablo araması ve bir yapıcının maliyetidir. Söylediği gibi, inşaatı optimize edebilirsiniz.

ConcurrentDictionary<Type, Func<object>>.

1
Hmm .. hala programcı adına, dağıtılmış işlemenin aktif olarak farkında olmak için çalışmayı gerektiren; Yöntemde yalnızca [Dağıtılmış] özniteliğini ayarlamalarına (ve ContextBoundObject alt sınıflamasına veya kalıtımına değil) dayanan bir çözüm arıyordum. Mono.Cecil veya bunun gibi bir şeyi kullanarak derlemelerde bazı derleme sonrası değişiklikler yapmam gerekebilir gibi görünüyor.
Haziran Rodos

Bunun olağan bir yol olduğunu söylemem. Bu yol, gerekli beceriler açısından basittir (CLR'yi anlamaya gerek yoktur), ancak değiştirilen her yöntem / sınıf için aynı adımların tekrarlanmasını gerektirir. Daha sonra bir şeyi değiştirmek isterseniz (örneğin, sadece önce değil, sonra bir kod çalıştırın), o zaman bunu N kez yapmanız gerekecektir (güvenli olmayan kodun bir kez yapılması gerekir). Yani 1 saatlik iş yerine N saatlik iş)
Eugene Gorbovoy
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.