गतिशील रूप से C # विधि की सामग्री को प्रतिस्थापित करें?


108

मैं जो करना चाहता हूं वह यह है कि एक C # पद्धति कैसे लागू होती है, जब इसे कहा जाता है, तो मैं इसे कुछ इस तरह लिख सकता हूं:

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

रन-टाइम में, मुझे उन विधियों का विश्लेषण करने में सक्षम होना चाहिए जिनके पास डिस्ट्रीब्यूटेड विशेषता है (जो मैं पहले से ही कर सकता हूं) और फिर फ़ंक्शन निष्पादित होने से पहले और फ़ंक्शन के वापस आने के बाद कोड डालें। इससे भी महत्वपूर्ण बात, मुझे इसे कोड को संशोधित किए बिना सक्षम करने की आवश्यकता है जहां समाधान को कॉल किया जाता है या फ़ंक्शन के प्रारंभ में (संकलन समय पर, रन-टाइम में ऐसा करना उद्देश्य है)।

फिलहाल मैंने इस बिट कोड का प्रयास किया है (मान लें कि टी टाइप है जिसे सॉल्यूशन में संग्रहित किया जाता है, और मी सॉल्यूशन का एक प्रकार है) :

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 केवल डायनेमिक मॉड्यूल पर काम करता है; उन लोगों को नहीं जो पहले ही विधानसभा में संकलित और संग्रहीत किए गए हैं।

इसलिए मैं एक तरीके से SwapMethodBody को प्रभावी ढंग से करने का एक तरीका खोज रहा हूं जो पहले से ही भरी हुई और निष्पादित विधानसभा में संग्रहीत है

ध्यान दें, यह एक समस्या नहीं है अगर मुझे पूरी तरह से डायनेमिक मॉड्यूल में विधि को कॉपी करना है, लेकिन इस मामले में मुझे आईएल भर में कॉपी करने का एक तरीका खोजने की आवश्यकता है और साथ ही सभी कॉल को हल करने के लिए अपडेट करें (जैसे कि वे नई प्रति को इंगित करेगा।


3
पहले से लोड किए गए तरीकों को स्वैप करना संभव नहीं है। अन्यथा Spring.Net को प्रॉक्सी और इंटरफेस के साथ अजीब चीजें बनाने की ज़रूरत नहीं होगी :-) इस प्रश्न को पढ़ें, यह आपकी समस्या के लिए स्पर्शरेखा है: stackoverflow.com/questions/25803/… (यदि आप इसे रोक सकते हैं, तो आप कुछ इस तरह कर सकते हैं -अपराध करें ... यदि आप 1 नहीं कर सकते हैं तो स्पष्ट रूप से आप 2 नहीं कर सकते हैं)।
xanatos

उस स्थिति में, क्या डायनेमिक मॉड्यूल में किसी विधि को कॉपी करने का एक तरीका है, और बाकी असेंबली को अपडेट करना है जो उस विधि को नई प्रतिलिपि को इंगित करता है?
जून रोड्स

वही पुरानी वही पुरानी। यदि यह आसानी से किया जा सकता है, तो सभी विभिन्न आईओसी कंटेनर शायद ऐसा करेंगे। वे ऐसा नहीं करते हैं-> 99% यह नहीं किया जा सकता है :-) (भयानक और असंख्य हैक के बिना)। एक ही उम्मीद है: उन्होंने सी # 5.0 में मेटाप्रोग्रामिंग और एसिंक्स का वादा किया था। Async हमने देखा है ... कुछ भी नहीं Metaprogramming ... लेकिन यह हो सकता है!
xanatos

1
आपने वास्तव में यह नहीं बताया है कि आप अपने आप को कुछ दर्दनाक क्यों करना चाहते हैं।
डैनियलऑफटैब

6
कृपया मेरा जवाब नीचे देखें। यह पूरी तरह से संभव है। कोड पर आप स्वयं नहीं और रनटाइम के दौरान। मुझे समझ में नहीं आता कि इतने सारे लोग यह क्यों संभव नहीं है।
एंड्रियास परडाइक

जवाबों:


201

प्रकटीकरण: सद्भाव एक पुस्तकालय है जिसे लिखा गया था और मेरे द्वारा बनाए रखा गया है, इस पोस्ट के लेखक।

हार्मनी 2 एक ओपन सोर्स लाइब्रेरी (MIT लाइसेंस) है जिसे रनटाइम के दौरान किसी भी तरह के मौजूदा C # तरीकों को बदलने, सजाने या संशोधित करने के लिए डिज़ाइन किया गया है। यह मुख्य फोकस मोनो या .NET में लिखे गए गेम और प्लगइन्स हैं। यह एक ही विधि के लिए कई परिवर्तनों का ध्यान रखता है - वे एक-दूसरे को अधिलेखित करने के बजाय जमा करते हैं।

यह प्रत्येक मूल विधि के लिए गतिशील प्रतिस्थापन विधि बनाता है और उन पर कोड उत्सर्जित करता है जो आरंभ और अंत में कस्टम तरीकों को कहते हैं। यह आपको मूल आईएल कोड और कस्टम अपवाद हैंडलर को संसाधित करने के लिए फिल्टर लिखने की भी अनुमति देता है जो मूल विधि के अधिक विस्तृत हेरफेर के लिए अनुमति देता है।

प्रक्रिया को पूरा करने के लिए, यह एक साधारण असेंबलर को मूल विधि के ट्रैम्पोलिन में लिखता है जो डायनामिक विधि को संकलित करने से उत्पन्न असेंबलर को इंगित करता है। यह विंडोज, मैकओएस और मोनो द्वारा समर्थित किसी भी लिनक्स पर 32/64 बिट के लिए काम करता है।

प्रलेखन यहाँ पाया जा सकता है

उदाहरण

( स्रोत )

मूल कोड

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

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

सद्भाव एनोटेशन के साथ पैचिंग

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

वैकल्पिक रूप से, प्रतिबिंब के साथ मैनुअल पैचिंग

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

स्रोत कोड पर एक नज़र था, बहुत दिलचस्प! क्या आप समझा सकते हैं (यहां और / या प्रलेखन में) कि कैसे विशिष्ट निर्देश काम करते हैं जो कि कूद (में Memory.WriteJump) करने के लिए उपयोग किए जाते हैं ?
टॉम

आंशिक रूप से मेरी स्वयं की टिप्पणी का जवाब देने के लिए: 48 B8 <QWord>एक QWord तत्काल मूल्य पर जाता है rax, फिर FF E0है jmp rax- वहाँ सब स्पष्ट! मेरा शेष प्रश्न E9 <DWord>मामले के बारे में है (एक निकट कूद): ऐसा लगता है कि इस मामले में निकट कूद संरक्षित है और संशोधन कूद के निशाने पर है; मोनो पहली बार में ऐसा कोड कब उत्पन्न करता है, और इसे यह विशेष उपचार क्यों मिलता है?
टॉम

1
जहाँ तक मैं बता सकता हूँ कि यह .NET Core 2 का समर्थन नहीं करता है, AppDomain.CurrentDomain.DefineDynamicAssembly
Max

1
मेरे एक मित्र, 0x0ade ने मेरे बारे में उल्लेख किया कि एक कम परिपक्व विकल्प है जो .NET कोर पर काम करता है, अर्थात् NuGet पर MonoMod.RuntimeDetour।
एंड्रियास परडेइक

1
अपडेट: System.Reflection.Emit के संदर्भ को शामिल करके, सद्भाव अब .NET कोर 3 के साथ ठीक संकलन और परीक्षण करता है
Andreas Pardeike

181

.NET 4 और इसके बाद के संस्करण के लिए

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
यह बहुत अधिक उत्थान के योग्य है। मेरे पास एक पूरी तरह से अलग परिदृश्य है लेकिन यह स्निपेट वही है जो मुझे सही दिशा में स्थापित करने के लिए आवश्यक था। धन्यवाद।
SC

2
@ लोगमैन शानदार जवाब। लेकिन मेरा सवाल है: डिबग मोड में क्या हो रहा है? और क्या केवल एक निर्देश को बदलना संभव है? उदाहरण के लिए अगर मैं बिना शर्त एक पर सशर्त कूदना चाहता हूं? AFAIK आप संकलित विधि की जगह ले रहे हैं, इसलिए यह निर्धारित करना आसान नहीं है कि हमें किस स्थिति को बदलना चाहिए ...
एलेक्स

2
@AlexZhukovskiy अगर आप इसे ढेर पर पोस्ट करना चाहते हैं और मुझे लिंक भेजें। मैं इसे देखूंगा और सप्ताहांत के बाद आपको जवाब दूंगा। मशीन मैं सप्ताहांत के बाद आपके प्रश्न पर भी गौर करूंगा।
लोगमैन

2
MSTest के साथ एकीकरण परीक्षण के लिए ऐसा करते समय मैंने जिन दो चीजों पर ध्यान दिया है: (1) जब आप इसके thisअंदर उपयोग करते हैं injectionMethod*()तो संकलन समय केInjection दौरान एक उदाहरण का उल्लेख करेंगे , लेकिन रनटाइम के दौरान एक उदाहरण ( उदाहरण के लिए यह सही है कि आप इंजेक्शन वाले सदस्यों का उपयोग करते हैं तरीका)। (२) किसी कारणवश यह भाग केवल तब काम कर रहा था जब कोई परीक्षण डिबग कर रहा था , लेकिन ऐसा परीक्षण चलाते समय नहीं जो डिबग-संकलित किया गया हो। मैंने हमेशा भाग का उपयोग करके समाप्त किया । मुझे समझ में नहीं आता कि यह क्यों काम करता है लेकिन यह करता है। Target#DEBUG#else
गुड नाइट नर्ड प्राइड

2
बहुत अच्छा। सब कुछ तोड़ने का समय! का उपयोग @GoodNightNerdPride Debugger.IsAttachedके बजाय #if पूर्वप्रक्रमक
M.kazem Akhgary

25

आप रनटाइम पर किसी विधि की सामग्री को संशोधित कर सकते हैं। लेकिन आप के लिए नहीं कर रहे हैं, और यह दृढ़ता से परीक्षण प्रयोजनों के लिए रखने के लिए सिफारिश की है।

बस एक नज़र है:

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

मूल रूप से, आप कर सकते हैं:

  1. MethodInfo.GetMethodBody () के माध्यम से IL विधि सामग्री प्राप्त करें। GetILAsByteArray ()
  2. इन बाइट्स के साथ मेस।

    यदि आप बस कुछ कोड को प्रीपेन्ड या अपेंड करना चाहते हैं, तो आप जो भी ओपकोड चाहते हैं उसे प्रीप्रेंड / अपेंड करें (स्टैक क्लीन छोड़ने के बारे में सावधान रहें, हालाँकि)

    यहाँ कुछ मौजूदा "IL" को हटाने के लिए सुझाव दिए गए हैं:

    • बाइट्स आईएल निर्देशों का एक क्रम है, उनके तर्क के बाद (यदि उनके पास कुछ है - उदाहरण के लिए, '.call' में एक तर्क है: जिसे विधि टोकन कहा जाता है, और '.pop' में कोई नहीं है)
    • आईएल कोड और बाइट्स के बीच पत्राचार जो आपको लौटाए गए सरणी में मिलते हैं, OpCodes.YourOpCode.Value (जो कि आपकी विधानसभा में सहेजे गए असली ओपोड बाइट मान है) का उपयोग करके पाया जा सकता है
    • आईएल कोड के बाद संलग्न तर्क अलग-अलग आकार (एक से कई बाइट्स) हो सकते हैं, जो कि ओपकोड नामक पर निर्भर करता है
    • आप टोकन पा सकते हैं कि तर्कों को उपयुक्त तरीकों से संदर्भित किया जा रहा है। उदाहरण के लिए, यदि आपका IL ".call 354354" है (हेक्सा में 28 00 05 68 32 के रूप में कोडित है, 28h = 40 जा रहा है '.call' opcode और 56832h = 354354), तो संबंधित विधि MethodBase.GetMethodFromHandle (3543544) का उपयोग करके पाया जा सकता है। )
  3. एक बार संशोधित होने पर, आप IL बाइट सरणी को InjectionHelper.UpdateILCodes (MethodInfo पद्धति, बाइट [ilCodes) के माध्यम से पुन: प्राप्त कर सकते हैं - ऊपर उल्लिखित लिंक देखें

    यह "असुरक्षित" हिस्सा है ... यह अच्छी तरह से काम करता है, लेकिन इसमें आंतरिक सीएलआर तंत्र को हैक करना शामिल है ...


7
सिर्फ पांडित्यपूर्ण होने के लिए, 354354 (0x00056832) एक वैध मेटाडेटा टोकन नहीं है, उच्च-क्रम बाइट 0x06 (मेथडिफ), 0x0A (मेम्बररीफ) या 0x2B (मेथस्पीक) होनी चाहिए। इसके अलावा, मेटाडेटा टोकन को छोटे-एंडियन बाइट क्रम में लिखा जाना चाहिए। अंत में, मेटाडेटा टोकन मॉड्यूल विशिष्ट है और MethodInfo.MetadataToken घोषित मॉड्यूल से टोकन लौटाएगा, यदि आप जिस विधि को संशोधित कर रहे हैं, उसी तरीके से परिभाषित नहीं किए जाने वाले तरीके को कॉल करना चाहते हैं तो यह अनुपयोगी हो जाएगा।
ब्रायन रीचेल

13

आप इसे बदल सकते हैं यदि विधि गैर आभासी है, गैर सामान्य है, सामान्य प्रकार में नहीं है, इनबिल्ट नहीं है और 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.

कि पागल खतरनाक लग रहा है। मैं वास्तव में आशा करता हूं कि कोई भी इसे उत्पादन कोड में उपयोग नहीं करेगा।
ब्रायन रीचेल

2
इसका उपयोग एप्लिकेशन प्रदर्शन निगरानी (APM) टूल द्वारा किया जाता है और इसका उपयोग उत्पादन में भी किया जाता है।
मार्टिन केर्स्टन

1
उत्तर के लिए धन्यवाद, मैं इस तरह की क्षमता की पेशकश करने के लिए एक परियोजना पर काम कर रहा हूं जो कि एस्पेक्ट ओरिएंटेड प्रोग्रामिंग एपीआई है। मैंने x86 & x64 दोनों पर वर्चुअल विधि और जेनेरिक विधि को प्रबंधित करने के लिए अपनी सीमा को हल किया। यदि आपको अधिक विवरण की आवश्यकता है, तो मुझे बताएं।
Teter28

6
वर्ग मेटाडाटा क्या है?
सेबेस्टियन

यह उत्तर छद्म कोड और पुराना है। कई विधियाँ अब मौजूद नहीं हैं।
एन-

9

ऐसे कुछ ढाँचे मौजूद हैं जो आपको रनटाइम के दौरान किसी भी तरीके को गतिशील रूप से बदलने की अनुमति देते हैं (वे user152949 द्वारा उल्लिखित ICLRProfiling इंटरफ़ेस का उपयोग करते हैं):

कुछ फ्रेमवर्क भी हैं जो .NET के इंटर्नल के साथ घूमते हैं, ये अधिक नाजुक होते हैं, और शायद इनबिल्ट कोड को बदल नहीं सकते हैं, लेकिन दूसरी तरफ वे पूरी तरह से आत्म-निहित होते हैं और आपको इसका उपयोग करने की आवश्यकता नहीं होती है कस्टम लांचर।

  • सद्भाव : एमआईटी लाइसेंस प्राप्त है। ऐसा लगता है कि वास्तव में कुछ गेम मोड में पर्याप्त रूप से उपयोग किया गया है, .NET और मोनो दोनों का समर्थन करता है।
  • Deviare इन प्रोसेस इंस्ट्रूमेंटेशन इंजन : GPLv3 और वाणिज्यिक। .NET समर्थन वर्तमान में प्रयोगात्मक के रूप में चिह्नित है, लेकिन दूसरी ओर व्यावसायिक रूप से समर्थित होने का लाभ है।

8

लॉगमैन का समाधान , लेकिन स्वैपिंग विधि निकायों के लिए एक इंटरफ़ेस के साथ। इसके अलावा, एक सरल उदाहरण।

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
इसने मुझे दिया: MA.ELCalc.FunctionalTests.dll में 'System.AccessViolationException' प्रकार का एक अपवाद उत्पन्न हुआ, लेकिन उपयोगकर्ता कोड में संभाला नहीं गया था अतिरिक्त जानकारी: संरक्षित स्मृति को पढ़ने या लिखने का प्रयास किया गया। यह अक्सर एक संकेत है कि अन्य मेमोरी भ्रष्ट है। ,,, जब एक गटर की जगह।
एन-

मुझे अपवाद मिला "wapMethodBodies अभी तक 8 के IntPtr आकार को संभालता नहीं है"
फोंग दाओ

6

इस सवाल के जवाब के आधार पर और ive, इस tidied अप संस्करण के साथ आया:

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

फिलहाल यह सबसे अच्छा जवाब है
यूजीन गोर्बोवॉय

एक उपयोग उदाहरण जोड़ने के लिए उपयोगी होगा
कोफिफ़स

5

आप ICLRPRofiling इंटरफ़ेस का उपयोग करके रनटाइम में एक विधि को बदल सकते हैं ।

  1. प्रक्रिया में संलग्न करने के लिए AttachProfiler को कॉल करें ।
  2. विधि कोड को बदलने के लिए SetILFunctionBody पर कॉल करें ।

अधिक जानकारी के लिए इस ब्लॉग को देखें।


3

मुझे पता है कि यह आपके प्रश्न का सटीक उत्तर नहीं है, लेकिन इसे करने का सामान्य तरीका कारखानों / प्रॉक्सी दृष्टिकोण का उपयोग करना है।

पहले हम आधार प्रकार घोषित करते हैं।

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

तब हम एक व्युत्पन्न प्रकार (इसे प्रॉक्सी कह सकते हैं) घोषित कर सकते हैं।

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

व्युत्पन्न प्रकार को रनटाइम पर भी उत्पन्न किया जा सकता है।

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

केवल प्रदर्शन हानि व्युत्पन्न वस्तु के निर्माण के दौरान है, पहली बार काफी धीमा है क्योंकि यह बहुत अधिक प्रतिबिंब और प्रतिबिंब का उपयोग करेगा। अन्य सभी समय में, यह एक समवर्ती टेबल लुकअप और एक कंस्ट्रक्टर की लागत है। जैसा कि कहा गया है, आप निर्माण का उपयोग करके अनुकूलन कर सकते हैं

ConcurrentDictionary<Type, Func<object>>.

1
हम्म .. कि प्रोग्रामर की ओर से अभी भी सक्रिय रूप से वितरित प्रसंस्करण के बारे में जागरूक होने के लिए काम करने की आवश्यकता है; मैं एक समाधान की तलाश में था जो केवल उन पर [वितरण] विशेषता को स्थापित करने पर निर्भर करता है (और ContextBoundObject से उप-वर्ग या विरासत में नहीं मिला)। ऐसा लगता है कि मुझे Mono.Cecil या ऐसा कुछ का उपयोग करके असेंबली पर कुछ संकलन संकलन करने की आवश्यकता हो सकती है।
जून रोड्स

मैं यह नहीं कहूंगा, कि यह सामान्य तरीका है। आवश्यक कौशल (CLR को समझने की आवश्यकता नहीं है) के संदर्भ में यह तरीका सरल है, लेकिन इसके लिए प्रतिस्थापित पद्धति / वर्ग में से प्रत्येक के लिए समान चरणों को दोहराना होगा। यदि बाद में आप कुछ बदलना चाहते हैं (उदाहरण के लिए, कुछ कोड को बाद में निष्पादित करें, न केवल पहले) तो आपको इसे एन बार करना होगा (असुरक्षित कोड के विपरीत जो एक बार करना होगा)। तो यह एन घंटे की नौकरी बनाम 1 घंटे की नौकरी है)
यूजीन गोर्बोवय
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.