Dynamicznie zamieniać zawartość metody C #?


110

Chcę zmienić sposób wykonywania metody C #, gdy jest wywoływana, aby móc napisać coś takiego:

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

W czasie wykonywania muszę być w stanie analizować metody, które mają atrybut Distributed (co już mogę zrobić), a następnie wstawiać kod przed wykonaniem treści funkcji i po jej powrocie. Co ważniejsze, muszę być w stanie to zrobić bez modyfikowania kodu w miejscu wywołania funkcji Solve lub na początku funkcji (w czasie kompilacji; celem jest zrobienie tego w czasie wykonywania).

W tej chwili próbowałem tego fragmentu kodu (załóżmy, że t jest typem, w którym jest przechowywany Solve, a m to 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;
}

Jednak MethodRental.SwapMethodBody działa tylko na modułach dynamicznych; nie te, które zostały już skompilowane i zapisane w zestawie.

Dlatego szukam sposobu, aby skutecznie wykonać SwapMethodBody w metodzie, która jest już przechowywana w załadowanym i wykonującym się zestawie .

Zauważ, że nie jest problemem, jeśli muszę całkowicie skopiować metodę do modułu dynamicznego, ale w tym przypadku muszę znaleźć sposób na skopiowanie przez IL, a także zaktualizować wszystkie wywołania do Solve (), tak aby były wskazywałby na nową kopię.


3
Nie można zamienić już załadowanych metod. W przeciwnym razie Spring.Net nie musiałby robić dziwnych rzeczy z serwerami proxy i interfejsami :-) Przeczytaj to pytanie, jest styczne do twojego problemu: stackoverflow.com/questions/25803/ ... (jeśli możesz to przechwycić, możesz coś w rodzaju - zamień to ... Jeśli nie możesz 1, to oczywiście nie możesz 2).
xanatos

W takim przypadku czy istnieje sposób na skopiowanie metody do modułu dynamicznego i zaktualizowanie pozostałej części zestawu w taki sposób, że wywołania tej metody wskazują na nową kopię?
czerwiec Rodos,

Ten sam stary, taki sam stary. Gdyby można było to łatwo zrobić, prawdopodobnie zrobiłyby to wszystkie różne kontenery IoC. Nie robią tego -> 99% nie da się tego zrobić :-) (bez okropnych i niewidzialnych włamań). Jest jedna nadzieja: obiecali metaprogramowanie i asynchroniczność w C # 5.0. Async widzieliśmy ... nic nie metaprogramowanie ... ALE to może być to!
xanatos

1
Naprawdę nie wyjaśniłeś, dlaczego chcesz pozwolić sobie na coś tak bolesnego.
DanielOfTaebl

6
Zobacz moją odpowiedź poniżej. Jest to całkowicie możliwe. Na kodzie, którego nie jesteś właścicielem i podczas działania. Nie rozumiem, dlaczego tak wielu uważa, że ​​to niemożliwe.
Andreas Pardeike,

Odpowiedzi:


203

Ujawnienie: Harmony to biblioteka, która została napisana i jest utrzymywana przeze mnie, autora tego postu.

Harmony 2 to biblioteka open source (licencja MIT) zaprojektowana do zastępowania, dekorowania lub modyfikowania istniejących metod języka C # dowolnego rodzaju w czasie wykonywania. Jej głównym celem są gry i wtyczki napisane w Mono lub .NET. Dba o wiele zmian w tej samej metodzie - kumulują się, a nie zastępują się nawzajem.

Tworzy dynamiczne metody zastępcze dla każdej oryginalnej metody i emituje do nich kod, który wywołuje metody niestandardowe na początku i na końcu. Umożliwia także pisanie filtrów do przetwarzania oryginalnego kodu IL i niestandardowych procedur obsługi wyjątków, co pozwala na bardziej szczegółowe manipulowanie oryginalną metodą.

Aby zakończyć proces, zapisuje prosty asemblerowy skok do trampoliny oryginalnej metody, który wskazuje na asemblera wygenerowanego z kompilacji metody dynamicznej. Działa to dla 32 / 64Bit w systemie Windows, macOS i dowolnym systemie Linux, który obsługuje Mono.

Dokumentację można znaleźć tutaj .

Przykład

( Źródło )

Oryginalny kod

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

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

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

Alternatywnie, ręczne łatanie z refleksją

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

Rzuciłem okiem na kod źródłowy, bardzo interesujące! Czy możesz wyjaśnić (tutaj i / lub w dokumentacji), jak działają określone instrukcje, które są używane do wykonania skoku (do Memory.WriteJump)?
Tom

Aby częściowo odpowiedzieć na mój własny komentarz: 48 B8 <QWord>przesuwa natychmiastową wartość QWord na rax, a następnie FF E0jest jmp rax- wszystko jasne! Moje pozostałe pytanie dotyczy E9 <DWord>przypadku (skok z bliska): wydaje się, że w tym przypadku skok z bliska jest zachowany, a modyfikacja jest na celu skoku; kiedy Mono generuje taki kod w pierwszej kolejności i dlaczego otrzymuje to specjalne traktowanie?
Tom

1
O ile wiem, nie obsługuje jeszcze .NET Core 2, dostaję kilka wyjątków z AppDomain.CurrentDomain.DefineDynamicAssembly
maks.

1
Mój przyjaciel, 0x0ade, wspomniał mi, że istnieje mniej dojrzała alternatywa, która działa na .NET Core, a mianowicie MonoMod.RuntimeDetour na NuGet.
Andreas Pardeike

1
Aktualizacja:
dodając

181

Dla .NET 4 i nowszych

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
To zasługuje na dużo więcej głosów poparcia. Mam zupełnie inny scenariusz, ale ten fragment jest dokładnie tym, czego potrzebowałem, aby skierować mnie we właściwym kierunku. Dzięki.
SC

2
@Logman świetna odpowiedź. Ale moje pytanie brzmi: co się dzieje w trybie debugowania? A czy można zastąpić tylko jedną instrukcję? Na przykład, jeśli chcę zamienić skok warunkowy na bezwarunkowy? AFAIK, że zastępujesz skompilowaną metodę, więc nie jest łatwo określić, który stan powinniśmy zastąpić ...
Alex Zhukovskiy

2
@AlexZhukovskiy, jeśli chcesz, umieść to na stosie i wyślij mi link. Przyjrzę się temu i udzielę odpowiedzi po weekendzie. Maszyna Po weekendzie zajrzę również do twojego pytania.
Logman,

2
Dwie rzeczy, które zauważyłem, robiąc to dla testu integracji z MSTest: (1) Kiedy używasz thiswewnątrz injectionMethod*(), będzie on odwoływał się do Injectioninstancji w czasie kompilacji , ale Targetinstancji w czasie wykonywania (dotyczy to wszystkich odwołań do elementów instancji, których używasz wewnątrz wstrzykniętego metoda). (2) Z jakiegoś powodu #DEBUGczęść działała tylko podczas debugowania testu, ale nie podczas uruchamiania testu, który został skompilowany przez debugowanie. Skończyło się na tym, że zawsze korzystałem z tej #elseczęści. Nie rozumiem, dlaczego to działa, ale działa.
Good Night Nerd Pride,

2
bardzo dobrze. czas wszystko zepsuć! @GoodNightNerdPride Debugger.IsAttachedzamiast #if preprocesora
M.kazem Akhgary

25

Możesz modyfikować zawartość metody w czasie wykonywania. Ale nie powinieneś tego robić i zdecydowanie zaleca się zachowanie tego do celów testowych.

Wystarczy spojrzeć na:

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

Zasadniczo możesz:

  1. Pobierz zawartość metody IL za pomocą MethodInfo.GetMethodBody (). GetILAsByteArray ()
  2. Zadzieraj z tymi bajtami.

    Jeśli chcesz po prostu dodać lub dołączyć jakiś kod, po prostu poprzedzaj / dołączaj opkody, które chcesz (jednak uważaj na pozostawienie stosu czystego)

    Oto kilka wskazówek, jak „zdekompilować” istniejący IL:

    • Zwrócone bajty to sekwencja instrukcji IL, po których następują ich argumenty (jeśli mają jakieś - na przykład „.call” ma jeden argument: wywołany token metody, a „.pop” nie ma żadnego)
    • Korespondencję między kodami IL i bajtami, które znajdziesz w zwróconej tablicy, można znaleźć za pomocą OpCodes.YourOpCode.Value (który jest rzeczywistą wartością bajtu opcode zapisaną w twoim zestawie)
    • Argumenty dołączane po kodach IL mogą mieć różne rozmiary (od jednego do kilku bajtów), w zależności od wywoływanego kodu
    • Możesz znaleźć tokeny, do których odwołują się te argumenty za pomocą odpowiednich metod. Na przykład, jeśli Twój IL zawiera „.call 354354” (zakodowany jako 28 00 05 68 32 w kodzie szesnastkowym, 28h = 40 to kod operacji „.call” i 56832h = 354354), odpowiednią wywołaną metodę można znaleźć za pomocą MethodBase.GetMethodFromHandle (354354 )
  3. Po zmodyfikowaniu tablica bajtów IL może zostać ponownie wprowadzona za pomocą InjectionHelper.UpdateILCodes (metoda MethodInfo, byte [] ilCodes) - patrz link wspomniany powyżej

    To jest ta „niebezpieczna” część ... Działa dobrze, ale polega to na włamywaniu się do wewnętrznych mechanizmów CLR ...


7
Aby być pedantycznym, 354354 (0x00056832) nie jest prawidłowym tokenem metadanych, bajt wyższego rzędu powinien mieć wartość 0x06 (MethodDef), 0x0A (MemberRef) lub 0x2B (MethodSpec). Ponadto token metadanych powinien być zapisany w kolejności bajtów little-endian. Wreszcie token metadanych jest specyficzny dla modułu, a MethodInfo.MetadataToken zwróci token z modułu deklarującego, co czyni go bezużytecznym, jeśli chcesz wywołać metodę, która nie jest zdefiniowana w tym samym module co metoda, którą modyfikujesz.
Brian Reichle,

13

możesz go zastąpić, jeśli metoda jest niewirtualna, nie generyczna, nie jest w typie ogólnym, nie jest w wierszu i na platformie 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.

To wygląda szalenie niebezpiecznie. Mam nadzieję, że nikt nie użyje go w kodzie produkcyjnym.
Brian Reichle

2
Jest to używane przez narzędzia do monitorowania wydajności aplikacji (APM), a także w produkcji.
Martin Kersten,

1
Dziękuję za odpowiedź, pracuję nad projektem, który ma oferować takie możliwości jako API programowania zorientowanego na aspekty. Rozwiązałem moje ograniczenie do zarządzania metodą wirtualną i metodą ogólną na obu x86 i x64. Daj mi znać, jeśli potrzebujesz więcej informacji.
Teter

6
Co to są metadane klasy?
Sebastian,

Ta odpowiedź jest pseudo kodem i nieaktualna. Wiele metod już nie istnieje.
Zjadł

9

Istnieje kilka frameworków, które pozwalają dynamicznie zmieniać dowolną metodę w czasie wykonywania (używają interfejsu ICLRProfiling wspomnianego przez user152949):

Istnieje również kilka frameworków, które kpią z wewnętrznych elementów .NET, są one prawdopodobnie bardziej delikatne i prawdopodobnie nie mogą zmienić wbudowanego kodu, ale z drugiej strony są w pełni samodzielne i nie wymagają używania niestandardowy program uruchamiający.

  • Harmony : licencja MIT. Wydaje się, że faktycznie został pomyślnie użyty w kilku modach do gier, obsługuje zarówno .NET, jak i Mono.
  • Deviare In Process Instrumentation Engine : GPLv3 i wersja komercyjna. Obsługa .NET jest obecnie oznaczona jako eksperymentalna, ale z drugiej strony ma tę zaletę, że jest wspierana komercyjnie.

8

Rozwiązanie Logmana , ale z interfejsem do zamiany treści metod. Również prostszy przykład.

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
Dało mi to: Wyjątek typu „System.AccessViolationException” wystąpił w MA.ELCalc.FunctionalTests.dll, ale nie został obsłużony w kodzie użytkownika. Dodatkowe informacje: Próbowano odczytać lub zapisać chronioną pamięć. Często jest to wskazanie, że inna pamięć jest uszkodzona. ,,, Podczas wymiany gettera.
Zjadłem

Wystąpił wyjątek „wapMethodBodies nie obsługuje jeszcze rozmiaru IntPtr 8”
Phong Dao

6

Opierając się na odpowiedzi na to i inne pytanie, wymyśliłem tę uporządkowaną wersję:

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

W tej chwili ta jest najlepszą odpowiedzią
Eugene Gorbovoy

pomocne byłoby dodanie przykładu użycia
kofifus


3

Wiem, że to nie jest dokładna odpowiedź na twoje pytanie, ale zwykle można to zrobić przy użyciu podejścia opartego na fabrykach / proxy.

Najpierw deklarujemy typ podstawowy.

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

Następnie możemy zadeklarować typ pochodny (nazwijmy go 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();

Typ pochodny można również wygenerować w czasie wykonywania.

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

Jedyna utrata wydajności występuje podczas konstruowania obiektu pochodnego, za pierwszym razem jest dość powolny, ponieważ zużywa dużo odbić i emituje odbicia. We wszystkich innych przypadkach jest to koszt współbieżnego wyszukiwania w tabeli i konstruktora. Jak już powiedziano, możesz zoptymalizować konstrukcję za pomocą

ConcurrentDictionary<Type, Func<object>>.

1
Hmm… to nadal wymaga pracy w imieniu programisty, aby aktywnie zdawać sobie sprawę z rozproszonego przetwarzania; Szukałem rozwiązania, które polega tylko na ustawieniu atrybutu [Distributed] w metodzie (a nie na podklasie lub dziedziczeniu po ContextBoundObject). Wygląda na to, że może być konieczne wykonanie pewnych modyfikacji po kompilacji na zespołach przy użyciu Mono.Cecil lub czegoś podobnego.
czerwiec Rhodes,

Nie powiedziałbym, że to zwykły sposób. Ten sposób jest prosty pod względem wymaganych umiejętności (nie ma potrzeby rozumienia CLR), ale wymaga powtórzenia tych samych kroków dla każdej zastępowanej metody / klasy. Jeśli później będziesz chciał coś zmienić (na przykład wykonać jakiś kod po, ​​a nie tylko przed), będziesz musiał to zrobić N razy (w przeciwieństwie do niebezpiecznego kodu, który wymaga zrobienia tego raz). Więc to N godzin pracy vs 1 godzina pracy)
Eugene Gorbovoy
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.