Den Inhalt einer C # -Methode dynamisch ersetzen?

108

Ich möchte ändern, wie eine C # -Methode beim Aufruf ausgeführt wird, damit ich so etwas schreiben kann:

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

Zur Laufzeit muss ich in der Lage sein, Methoden mit dem Attribut Distributed zu analysieren (was ich bereits kann) und dann Code einzufügen, bevor der Hauptteil der Funktion ausgeführt wird und nachdem die Funktion zurückgegeben wird. Noch wichtiger ist, dass ich in der Lage sein muss, dies zu tun, ohne den Code zu ändern, in dem Solve aufgerufen wird, oder zu Beginn der Funktion (zur Kompilierungszeit; dies ist zur Laufzeit das Ziel).

Im Moment habe ich dieses Codebit versucht (angenommen, t ist der Typ, in dem Solve gespeichert ist, und m ist eine MethodInfo von 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;
}

MethodRental.SwapMethodBody funktioniert jedoch nur mit dynamischen Modulen. nicht diejenigen, die bereits kompiliert und in der Assembly gespeichert wurden.

Daher suche ich nach einer Möglichkeit, SwapMethodBody effektiv für eine Methode auszuführen, die bereits in einer geladenen und ausgeführten Assembly gespeichert ist .

Beachten Sie, dass es kein Problem ist, wenn ich die Methode vollständig in ein dynamisches Modul kopieren muss. In diesem Fall muss ich jedoch eine Möglichkeit finden, über die IL zu kopieren und alle Aufrufe von Solve () so zu aktualisieren, dass sie würde auf die neue Kopie verweisen.

Juni Rhodos
quelle
3
Bereits geladene Methoden können nicht ausgetauscht werden. Andernfalls müsste Spring.Net keine seltsamen Dinge mit Proxys und Schnittstellen machen :-) Lesen Sie diese Frage, sie betrifft Ihr Problem: stackoverflow.com/questions/25803/… (wenn Sie sie abfangen können, können Sie so etwas tun -swap it ... Wenn Sie nicht 1 können, dann können Sie natürlich nicht 2).
Xanatos
Gibt es in diesem Fall eine Möglichkeit, eine Methode in ein dynamisches Modul zu kopieren und den Rest der Assembly so zu aktualisieren, dass Aufrufe dieser Methode auf die neue Kopie verweisen?
Juni Rhodos
Gleich alt, gleich alt. Wenn es einfach wäre, würden es wahrscheinlich alle verschiedenen IoC-Container tun. Sie machen es nicht -> 99% können es nicht machen :-) (ohne schreckliche und unerträgliche Hacks). Es gibt eine einzige Hoffnung: Sie versprachen Metaprogrammierung und Asynchronisation in C # 5.0. Async haben wir gesehen ... Metaprogrammieren nichts ... ABER es könnte es sein!
Xanatos
1
Sie haben wirklich nicht erklärt, warum Sie sich auf etwas so Schmerzhaftes einlassen wollen.
DanielOfTaebl
6
Bitte sehen Sie meine Antwort unten. Das ist durchaus möglich. Auf Code, den Sie nicht besitzen und zur Laufzeit. Ich verstehe nicht, warum so viele denken, dass dies nicht möglich ist.
Andreas Pardeike

Antworten:

201

Offenlegung: Harmony ist eine Bibliothek, die von mir, dem Autor dieses Beitrags, geschrieben und gepflegt wird.

Harmony 2 ist eine Open Source-Bibliothek (MIT-Lizenz), mit der vorhandene C # -Methoden zur Laufzeit ersetzt, dekoriert oder geändert werden können. Das Hauptaugenmerk liegt auf Spielen und Plugins, die in Mono oder .NET geschrieben wurden. Es werden mehrere Änderungen an derselben Methode vorgenommen - sie sammeln sich an, anstatt sich gegenseitig zu überschreiben.

Es erstellt dynamische Ersetzungsmethoden für jede ursprüngliche Methode und gibt Code an diese aus, der am Anfang und am Ende benutzerdefinierte Methoden aufruft. Außerdem können Sie Filter schreiben, um den ursprünglichen IL-Code und benutzerdefinierte Ausnahmebehandlungsroutinen zu verarbeiten, wodurch die ursprüngliche Methode detaillierter bearbeitet werden kann.

Um den Vorgang abzuschließen, wird ein einfacher Assembler-Sprung in das Trampolin der ursprünglichen Methode geschrieben, der auf den Assembler verweist, der beim Kompilieren der dynamischen Methode generiert wurde. Dies funktioniert für 32 / 64Bit unter Windows, MacOS und jedem Linux, das Mono unterstützt.

Dokumentation finden Sie hier .

Beispiel

( Quelle )

Originalcode

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

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

Patchen mit Harmony-Anmerkungen

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

Alternativ manuelles Patchen mit Reflexion

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
quelle
Werfen Sie einen Blick auf den Quellcode, sehr interessant! Können Sie (hier und / oder in der Dokumentation) erklären, wie die spezifischen Anweisungen funktionieren, mit denen der Sprung (in Memory.WriteJump) ausgeführt wird?
Tom
Um meinen eigenen Kommentar teilweise zu beantworten: 48 B8 <QWord>Verschiebt einen QWord-Sofortwert auf rax, dann FF E0ist jmp rax- alles klar da! Meine verbleibende Frage bezieht sich auf den E9 <DWord>Fall (ein Nahsprung): In diesem Fall scheint der Nahsprung erhalten zu sein und die Modifikation befindet sich auf dem Ziel des Sprunges; Wann generiert Mono überhaupt einen solchen Code und warum wird er speziell behandelt?
Tom
1
Soweit ich das beurteilen kann, wird .NET Core 2 noch nicht unterstützt, es werden einige Ausnahmen mit AppDomain.CurrentDomain.DefineDynamicAssembly
Max
1
Ein Freund von mir, 0x0ade, hat mir gegenüber erwähnt, dass es eine weniger ausgereifte Alternative gibt, die auf .NET Core funktioniert, nämlich MonoMod.RuntimeDetour auf NuGet.
Andreas Pardeike
1
Update: Durch die Aufnahme eines Verweises auf System.Reflection.Emit kompiliert und testet Harmony jetzt OK mit .NET Core 3
Andreas Pardeike
181

Für .NET 4 und höher

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

}
Logman
quelle
14
Dies verdient so viel mehr Gegenstimmen. Ich habe ein völlig anderes Szenario, aber dieses Snippet ist genau das, was ich brauchte, um mich in die richtige Richtung zu bewegen. Vielen Dank.
SC
2
@ Logman tolle Antwort. Aber meine Frage ist: Was ist im Debug-Modus los? Und ist es möglich, nur eine Anweisung zu ersetzen? Zum Beispiel, wenn ich den bedingten Sprung durch einen bedingungslosen ersetzen möchte? AFAIK Sie ersetzen kompilierte Methode, so ist es nicht einfach zu bestimmen, welche Bedingung wir ersetzen sollten ...
Alex Zhukovskiy
2
@AlexZhukovskiy wenn du es gerne auf Stack postest und mir einen Link schickst. Ich werde mich darum kümmern und dir nach dem Wochenende eine Antwort geben. Maschine Ich werde mich auch nach dem Wochenende mit Ihrer Frage befassen.
Logman
2
Zwei Dinge , die ich bemerkt , wenn dies für eine Integrationstest mit MSTest tun: (1) Wenn Sie thisinnerhalb injectionMethod*()es wird eine Referenz - InjectionInstanz während der Kompilierung , sondern eine TargetInstanz während der Laufzeit (dies gilt für alle Verweise auf Instanz Mitglieder , die Sie innerhalb eines injizierten verwenden Methode). (2) Aus irgendeinem Grund #DEBUGfunktionierte das Teil nur beim Debuggen eines Tests, nicht jedoch beim Ausführen eines Tests, der vom Debug kompiliert wurde. Am Ende habe ich immer das #elseTeil benutzt. Ich verstehe nicht, warum das funktioniert, aber es funktioniert.
Gute Nacht Nerd Pride
2
Sehr schön. Zeit, alles zu brechen! @ GoodNightNerdPride Verwendung Debugger.IsAttachedanstelle von #if Präprozessor
M. Kazem Akhgary 25.
25

Sie können den Inhalt einer Methode zur Laufzeit ändern. Dies ist jedoch nicht vorgesehen, und es wird dringend empfohlen, dies zu Testzwecken aufzubewahren.

Schauen Sie sich einfach an:

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

Grundsätzlich können Sie:

  1. Abrufen des Inhalts der IL-Methode über MethodInfo.GetMethodBody (). GetILAsByteArray ()
  2. Leg dich mit diesen Bytes an.

    Wenn Sie nur Code voranstellen oder anhängen möchten, stellen Sie einfach die gewünschten Opcodes vor / fügen Sie sie hinzu (achten Sie jedoch darauf, dass der Stapel sauber bleibt).

    Hier sind einige Tipps zum "Dekompilieren" vorhandener IL:

    • Die zurückgegebenen Bytes sind eine Folge von IL-Anweisungen, gefolgt von ihren Argumenten (wenn sie welche haben - zum Beispiel hat '.call' ein Argument: das aufgerufene Methodentoken und '.pop' hat keines)
    • Die Korrespondenz zwischen IL-Codes und Bytes, die Sie im zurückgegebenen Array finden, kann mithilfe von OpCodes.YourOpCode.Value gefunden werden (dies ist der tatsächliche Opcode-Byte-Wert, der in Ihrer Assembly gespeichert wurde).
    • Nach IL-Codes angehängte Argumente können je nach aufgerufenem Opcode unterschiedliche Größen haben (von einem bis zu mehreren Bytes)
    • Möglicherweise finden Sie Token, auf die sich diese Argumente über geeignete Methoden beziehen. Wenn Ihre IL beispielsweise ".call 354354" enthält (codiert als 28 00 05 68 32 in hexa, 28h = 40 ist ".call" -Opcode und 56832h = 354354), kann die entsprechende aufgerufene Methode mit MethodBase.GetMethodFromHandle (354354) gefunden werden )
  3. Nach der Änderung kann Ihr IL-Byte-Array über InjectionHelper.UpdateILCodes (MethodInfo-Methode, Byte [] ilCodes) erneut injiziert werden - siehe oben genannten Link

    Dies ist der "unsichere" Teil ... Es funktioniert gut, aber dies besteht darin, interne CLR-Mechanismen zu hacken ...

Olivier
quelle
7
Um pedantisch zu sein, ist 354354 (0x00056832) kein gültiges Metadaten-Token. Das höherwertige Byte sollte 0x06 (MethodDef), 0x0A (MemberRef) oder 0x2B (MethodSpec) sein. Außerdem sollte das Metadaten-Token in Little-Endian-Bytereihenfolge geschrieben werden. Schließlich ist das Metadaten-Token modulspezifisch und MethodInfo.MetadataToken gibt das Token vom deklarierenden Modul zurück. Dies macht es unbrauchbar, wenn Sie eine Methode aufrufen möchten, die nicht im selben Modul wie die zu ändernde Methode definiert ist.
Brian Reichle
13

Sie können es ersetzen, wenn die Methode nicht virtuell, nicht generisch, nicht generisch, nicht inline und auf x86-Plattenform ist:

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
quelle
Das sieht verrückt gefährlich aus. Ich hoffe wirklich, dass niemand es im Produktionscode verwendet.
Brian Reichle
2
Dies wird von APM-Tools (Application Performance Monitoring) verwendet und auch in der Produktion.
Martin Kersten
1
Vielen Dank für Ihre Antwort. Ich arbeite an einem Projekt, um diese Art von Funktion als aspektorientierte Programmier-API anzubieten. Ich habe meine Einschränkung behoben, virtuelle und generische Methoden sowohl auf x86 als auch auf x64 zu verwalten. Lassen Sie mich wissen, wenn Sie weitere Details benötigen.
Teter28
6
Was ist die Klasse Metadaten?
Sebastian
Diese Antwort ist Pseudocode und veraltet. Viele der Methoden existieren nicht mehr.
N-aß
9

Es gibt einige Frameworks, mit denen Sie jede Methode zur Laufzeit dynamisch ändern können (sie verwenden die von user152949 erwähnte ICLRProfiling-Schnittstelle):

Es gibt auch einige Frameworks, die sich über die Interna von .NET lustig machen. Diese sind wahrscheinlich anfälliger und können Inline-Code wahrscheinlich nicht ändern. Andererseits sind sie vollständig in sich geschlossen und erfordern keine Verwendung von a Benutzerdefinierter Launcher.

  • Harmony : MIT lizenziert. Scheint tatsächlich erfolgreich in einigen Spielmods verwendet worden zu sein, unterstützt sowohl .NET als auch Mono.
  • Deviare In Process Instrumentation Engine : GPLv3 und kommerziell. Die .NET-Unterstützung ist derzeit als experimentell gekennzeichnet, hat jedoch den Vorteil, dass sie kommerziell unterstützt wird.
poizan42
quelle
8

Logmans Lösung , jedoch mit einer Schnittstelle zum Austauschen von Methodenkörpern. Auch ein einfacheres Beispiel.

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
quelle
1
Dies gab mir: Eine Ausnahme vom Typ 'System.AccessViolationException' trat in MA.ELCalc.FunctionalTests.dll auf, wurde jedoch nicht im Benutzercode behandelt. Zusätzliche Informationen: Es wurde versucht, geschützten Speicher zu lesen oder zu schreiben. Dies ist oft ein Hinweis darauf, dass der andere Speicher beschädigt ist. ,,, Beim Ersetzen eines Getters.
N-ate
Ich habe die Ausnahme "wapMethodBodies verarbeitet noch keine IntPtr-Größe von 8"
Phong Dao
6

Basierend auf der Antwort auf diese und eine andere Frage habe ich mir diese aufgeräumte Version ausgedacht:

// 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
quelle
Im Moment ist dies die beste Antwort
Eugene Gorbovoy
wäre hilfreich, um ein Verwendungsbeispiel hinzuzufügen
kofifus
3

Ich weiß, dass dies nicht die genaue Antwort auf Ihre Frage ist, aber der übliche Weg, dies zu tun, ist die Verwendung eines Fabrik- / Proxy-Ansatzes.

Zuerst deklarieren wir einen Basistyp.

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

Dann können wir einen abgeleiteten Typ deklarieren (Proxy nennen).

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

Der abgeleitete Typ kann auch zur Laufzeit generiert werden.

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

Der einzige Leistungsverlust besteht während der Erstellung des abgeleiteten Objekts. Das erste Mal ist ziemlich langsam, da viel Reflexion und Reflexionsemission verwendet werden. In allen anderen Fällen sind dies die Kosten für eine gleichzeitige Tabellensuche und einen Konstruktor. Wie gesagt, Sie können die Konstruktion mit optimieren

ConcurrentDictionary<Type, Func<object>>.
Salvatore Previti
quelle
1
Hmm .. das erfordert immer noch Arbeit im Namen des Programmierers, um sich der verteilten Verarbeitung aktiv bewusst zu werden; Ich suchte nach einer Lösung, die nur darauf beruht, dass sie das Attribut [Distributed] für die Methode festlegen (und nicht von ContextBoundObject unterklassifizieren oder erben). Es sieht so aus, als müsste ich nach dem Kompilieren einige Änderungen an den Assemblys mit Mono.Cecil oder ähnlichem vornehmen.
Juni Rhodos
Ich würde nicht sagen, dass dies üblich ist. Dieser Weg ist einfach in Bezug auf die erforderlichen Fähigkeiten (keine Notwendigkeit, CLR zu verstehen), erfordert jedoch, dass für jede ersetzte Methode / Klasse die gleichen Schritte wiederholt werden. Wenn Sie später etwas ändern möchten (z. B. Code nach und nicht nur vorher ausführen), müssen Sie dies N-mal ausführen (im Gegensatz zu unsicherem Code, der einmal ausgeführt werden muss). Es ist also N Stunden Job gegen 1 Stunde Job)
Eugene Gorbovoy