C # 메서드의 내용을 동적으로 바꾸시겠습니까?


109

내가 원하는 것은 호출 될 때 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;
}

런타임에 Distributed 특성이있는 메서드 (이미 수행 할 수 있음)를 분석 한 다음 함수 본문이 실행되기 전과 함수가 반환 된 후에 코드를 삽입 할 수 있어야합니다. 더 중요한 것은 Solve가 호출되는 코드를 수정하거나 함수가 시작될 때 (컴파일 타임에, 런타임에 그렇게하는 것이 목표 임) 코드를 수정하지 않고 수행 할 수 있어야한다는 것입니다.

현재이 코드를 시도했습니다 (t는 Solve가 저장된 유형이고 m은 Solve의 MethodInfo라고 가정) .

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를 효과적으로 수행하는 방법을 찾고 있습니다.

메서드를 동적 모듈에 완전히 복사해야하는 경우 문제가되지 않지만,이 경우 IL을 통해 복사하는 방법을 찾고 Solve ()에 대한 모든 호출을 업데이트하여 새 사본을 가리킬 것입니다.


3
이미로드 된 메서드를 바꿀 수 없습니다. 그렇지 않으면 Spring.Net는 프록시와 인터페이스 이상한 일들을해야하지 않을 :-) 읽기이 질문, 문제에 대한 그것의 접선 : stackoverflow.com/questions/25803/...는 당신이 그것을 가로 챌 수있는 경우에 (, 당신이 뭔가 같이 할 수 -스왑 ... 1을 할 수 없다면 분명히 할 수 없습니다 2).
xanatos 2011 년

이 경우 메서드를 동적 모듈에 복사하고 해당 메서드에 대한 호출이 새 복사본을 가리 키도록 나머지 어셈블리를 업데이트하는 방법이 있습니까?
June Rhodes 2011 년

항상 그렇지 뭐. 쉽게 수행 할 수 있다면 모든 다양한 IoC 컨테이너가이를 수행 할 것입니다. 그들은 그것을하지 않습니다-> 99 % 그것은 할 수 없습니다 :-) (끔찍하고 명쾌한 해킹없이). 하나의 희망이 있습니다. 그들은 C # 5.0에서 메타 프로그래밍과 비동기를 약속했습니다. 우리가 본 비동기 ... 메타 프로그래밍은 아무것도 ...하지만 그럴 수 있습니다!
xanatos 2011 년

1
당신은 왜 그렇게 고통스러운 일을하고 싶은지 설명하지 않았습니다.
DanielOfTaebl 2011 년

6
아래 내 대답을 참조하십시오. 이것은 완전히 가능합니다. 소유하지 않은 코드와 런타임 중에. 나는 왜 그렇게 많은 사람들이 이것이 불가능하다고 생각하는지 이해하지 못합니다.
Andreas Pardeike

답변:


202

공개 : Harmony는이 게시물의 작성자 인 내가 작성하고 유지 관리하는 라이브러리입니다.

Harmony 2 는 런타임 중에 모든 종류의 기존 C # 메서드를 대체, 장식 또는 수정하도록 설계된 오픈 소스 라이브러리 (MIT 라이선스)입니다. 주요 초점은 Mono 또는 .NET으로 작성된 게임 및 플러그인입니다. 동일한 방법에 대한 여러 변경 사항을 처리합니다. 서로 덮어 쓰는 대신 누적됩니다.

모든 원본 메서드에 대한 동적 대체 메서드를 만들고 시작과 끝에서 사용자 지정 메서드를 호출하는 코드를 내 보냅니다. 또한 원본 IL 코드를 처리하는 필터를 작성하고 원본 메서드를보다 세부적으로 조작 할 수있는 사용자 지정 예외 처리기를 작성할 수 있습니다.

프로세스를 완료하기 위해 동적 메서드를 컴파일하여 생성 된 어셈블러를 가리키는 원래 메서드의 트램폴린으로 간단한 어셈블러 점프를 작성합니다. Windows, macOS 및 Mono가 지원하는 모든 Linux의 32 / 64Bit에서 작동합니다.

문서는 여기 에서 찾을 수 있습니다 .

( 출처 )

원래 코드

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

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

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

또는 리플렉션을 사용한 수동 패치

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?
Tom

부분적으로 내 자신의 의견을 대답하기 : 48 B8 <QWord>에 QWORD 즉시 값 이동 rax, 한 다음 FF E0입니다 jmp rax- 모든 분명 거기! 내 남은 질문은 E9 <DWord>사건 (근거리 점프) 에 관한 것입니다.이 경우에는 근거리 점프가 유지되고 수정이 점프 대상에있는 것 같습니다. Mono는 처음에 그러한 코드를 언제 생성하며 왜 이렇게 특별한 대우를 받습니까?
Tom

1
내가 알 수있는 한 아직 .NET Core 2를 지원하지 않습니다. AppDomain.CurrentDomain.DefineDynamicAssembly
Max

1
내 친구 0x0ade는 .NET Core에서 작동하는 덜 성숙한 대안, 즉 NuGet의 MonoMod.RuntimeDetour가 있다고 언급했습니다.
Andreas Pardeike 2019

1
업데이트 : System.Reflection.Emit에 대한 참조를 포함하여이 하모니는 이제 컴파일 및 테스트 OK와 .NET 코어 (3)
안드레아스 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
@Logman 좋은 대답. 하지만 내 질문은 : 디버그 모드에서 무슨 일이 일어나고 있습니까? 그리고 하나의 명령어 만 교체 할 수 있습니까? 예를 들어 무조건 점프에서 조건부 점프를 바꾸고 싶다면? AFAIK 컴파일 된 메서드를 대체하고 있으므로 어떤 조건을 대체해야하는지 결정하기가 쉽지 않습니다 ...
알렉스 쥬 코브 스키

2
@AlexZhukovskiy 스택에 게시하고 링크를 보내주세요. 주말이 지나면 조사해 드리겠습니다. 기계 나는 주말 후에 당신의 질문을 조사 할 것입니다.
Logman

2
MSTest와의 통합 테스트를 위해이 작업을 수행 할 때 알아 차린 두 가지 사항 : (1) this내부에서 사용하면 컴파일 시간 동안 인스턴스 injectionMethod*()를 참조 하지만 런타임 동안 인스턴스를 참조 합니다 (이는 삽입 된 내부에서 사용하는 인스턴스 멤버에 대한 모든 참조에 해당됩니다. 방법). (2) 어떤 이유로 테스트를 디버깅 할 때만 부품이 작동하고 디버그 컴파일 된 테스트를 실행할 때는 작동 하지 않았습니다 . 나는 항상 부품을 사용했습니다 . 나는 이것이 왜 작동하는지 이해하지 못하지만 작동합니다. InjectionTarget#DEBUG#else
굿나잇 얼간이 프라이드

2
아주 좋아. 모든 것을 깰 시간! 전 처리기 Debugger.IsAttached대신 @GoodNightNerdPride 사용#if
M.kazem Akhgary

25

런타임에 메소드의 내용을 수정할 수 있습니다. 하지만 그렇게해서는 안되며 테스트 목적으로 보관하는 것이 좋습니다.

다음을 살펴보십시오.

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

기본적으로 다음을 수행 할 수 있습니다.

  1. MethodInfo.GetMethodBody (). GetILAsByteArray ()를 통해 IL 메서드 콘텐츠 가져 오기
  2. 이 바이트와 엉망입니다.

    일부 코드를 앞에 추가하거나 추가하려면 원하는 opcode를 미리 추가 / 추가하십시오 (하지만 스택을 깨끗하게 유지하는 데주의하십시오).

    다음은 기존 IL을 "컴파일 해제"하는 몇 가지 팁입니다.

    • 반환되는 바이트는 일련의 IL 명령어와 그 뒤에 해당 인수입니다 (예를 들어 '.call'에는 하나의 인수가 있습니다. 호출 된 메서드 토큰이고 '.pop'에는 없음)
    • 반환 된 배열에서 찾은 IL 코드와 바이트 간의 대응은 OpCodes.YourOpCode.Value (어셈블리에 저장된 실제 opcode 바이트 값)를 사용하여 찾을 수 있습니다.
    • IL 코드 뒤에 추가 된 인수는 호출 된 opcode에 따라 크기가 다를 수 있습니다 (1 바이트에서 여러 바이트까지).
    • 이러한 인수가 적절한 방법을 통해 참조하는 토큰을 찾을 수 있습니다. 예를 들어, IL에 ".call 354354"(헥사에서 28 00 05 68 32로 코딩되고 28h = 40은 '.call'opcode이고 56832h = 354354로 코딩 됨)가 포함 된 경우 MethodBase.GetMethodFromHandle (354354)을 사용하여 해당 호출 된 메서드를 찾을 수 있습니다. )
  3. 수정되면 IL 바이트 배열은 InjectionHelper.UpdateILCodes (MethodInfo method, byte [] ilCodes)를 통해 다시 주입 할 수 있습니다.

    이것은 "안전하지 않은"부분입니다 ... 잘 작동하지만 내부 CLR 메커니즘을 해킹하는 것입니다 ...


7
현명하게 말하면 354354 (0x00056832)는 유효한 메타 데이터 토큰이 아니며 상위 바이트는 0x06 (MethodDef), 0x0A (MemberRef) 또는 0x2B (MethodSpec) 여야합니다. 또한 메타 데이터 토큰은 리틀 엔디안 바이트 순서로 작성해야합니다. 마지막으로 메타 데이터 토큰은 모듈에 따라 다르며 MethodInfo.MetadataToken은 선언 모듈에서 토큰을 반환하므로 수정중인 메서드와 동일한 모듈에 정의되지 않은 메서드를 호출하려는 경우 사용할 수 없게됩니다.
Brian Reichle

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.

정말 위험 해 보입니다. 아무도 프로덕션 코드에서 사용하지 않기를 바랍니다.
Brian Reichle

2
이는 애플리케이션 성능 모니터링 (APM) 도구에서 사용되며 프로덕션에서도 사용됩니다.
Martin Kersten 2015 년

1
답장을 보내 주셔서 감사합니다. 저는 이러한 종류의 기능을 Aspect Oriented Programming API로 제공하는 프로젝트를 진행하고 있습니다. x86 및 x64에서 가상 방법과 일반 방법을 관리하는 제한을 해결했습니다. 더 자세한 정보가 필요하면 알려주세요.
Teter28 2015 년

6
클래스 메타 데이터 란 무엇입니까?
세바스찬

이 답변은 의사 코드이며 구식입니다. 많은 방법이 더 이상 존재하지 않습니다.
N-먹고

9

런타임에 모든 메서드를 동적으로 변경할 수있는 몇 가지 프레임 워크가 있습니다 (사용자 152949에서 언급 한 ICLRProfiling 인터페이스 사용).

또한 .NET의 내부를 조롱하는 몇 가지 프레임 워크가 있습니다. 이러한 프레임 워크는 더 취약 할 가능성이 높고 인라인 코드를 변경할 수 없지만 다른 한편으로는 완전히 독립적이며 사용자가 커스텀 런처.

  • Harmony : MIT 라이센스. 실제로 몇 가지 게임 모드에서 성공적으로 사용 된 것으로 보이며 .NET과 Mono를 모두 지원합니다.
  • Deviare In Process Instrumentation Engine : GPLv3 및 Commercial. .NET 지원은 현재 실험적인 것으로 표시되어 있지만 반면에 상업적으로 지원된다는 이점이 있습니다.

8

Logman의 솔루션 이지만 메서드 본문을 교체하기위한 인터페이스가 있습니다. 또한 더 간단한 예입니다.

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'유형의 예외가 발생했지만 사용자 코드에서 처리되지 않았습니다. 추가 정보 : 보호 된 메모리를 읽거나 쓰려고했습니다. 이것은 종종 다른 메모리가 손상되었음을 나타냅니다. ,,, 게터를 교체 할 때.
N-ate

나는 "아직 8의 IntPtr입니다 크기를 처리하지 않습니다 wapMethodBodies"예외를 가지고
퐁 다오에게

6

이 질문과 다른 질문에 대한 답변을 바탕으로 ive는 다음과 같은 깔끔한 버전을 고안했습니다.

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

현재로서는 이것이 최선의 답변입니다
Eugene Gorbovoy

사용 예를 추가하면 도움이 될 것입니다
kofifus


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
흠 .. 분산 처리를 적극적으로 인식하려면 프로그래머를 대신하여 작업해야합니다. 메서드에 [Distributed] 특성을 설정하는 데만 의존하는 솔루션을 찾고있었습니다 (ContextBoundObject에서 상속하거나 서브 클래 싱하지 않음). Mono.Cecil 또는 이와 유사한 것을 사용하여 어셈블리에 대한 컴파일 후 수정 작업을 수행해야 할 수 있습니다.
June Rhodes 2011 년

나는 이것이 일반적인 방법이라고 말하지 않을 것입니다. 이 방법은 필요한 기술 측면에서 간단하지만 (CLR을 이해할 필요가 없음) 교체 된 각 메서드 / 클래스에 대해 동일한 단계를 반복해야합니다. 나중에 무언가를 변경하려면 (예 : 이전뿐만 아니라 이후에 일부 코드 실행) N 번 수행해야합니다 (한 번 수행해야하는 안전하지 않은 코드와는 대조적으로). 따라서 N 시간 작업 대 1 시간 작업)
Eugene Gorbovoy
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.