LoginSignup
26
21

More than 5 years have passed since last update.

C#でメソッドを動的に入れ替える

Last updated at Posted at 2015-01-10

というのをやってみたら、動いちゃったので、共有です。

サンプル

using UnityEngine;
using System;
using System.Collections;
using System.Reflection;

public class ReplaceMethod1 : MonoBehaviour
{
    static void ExchangeFunctionPointer(MethodInfo method0, MethodInfo method1)
    {
        unsafe
        {
            var functionPointer0 = method0.MethodHandle.Value.ToPointer();
            var functionPointer1 = method1.MethodHandle.Value.ToPointer();
            var tmpPointer = *((int*)new IntPtr(((int*)functionPointer0 + 1)).ToPointer());
            *((int*)new IntPtr(((int*)functionPointer0 + 1)).ToPointer()) = *((int*)new IntPtr(((int*)functionPointer1 + 1)).ToPointer());
            *((int*)new IntPtr(((int*)functionPointer1 + 1)).ToPointer()) = tmpPointer;
        }
    }

    void Awake()
    {
        MethodInfo onGuiMethod = this.GetType().GetMethod("OnGUI", BindingFlags.NonPublic | BindingFlags.Instance);
        MethodInfo onGuiV2Method = this.GetType().GetMethod("OnGUIv2", BindingFlags.NonPublic | BindingFlags.Instance);
        ExchangeFunctionPointer(onGuiMethod, onGuiV2Method);
    }

    void OnGUI()
    {
        if (GUILayout.Button("This is OnGUIv1"))
        {
        }
    }

    void OnGUIv2()
    {
        if (GUILayout.Button("This is OnGUIv2"))
        {
        }
    }
}

実行結果

スクリーンショット 2015-01-10 午後9.10.47.png
OnGUIの代わりに、OnGUIv2が呼び出されていますね。

てきとーな解説

やっている事は、
1. AwakeでリフレクションでOnGUI,OnGUIv2メソッドを取得して、ExchangeFunctionPointerメソッドに二つを投げ込む
2. ExchangeFunctionPointerメソッド内では、てきとーに実際の処理の関数ポインタを付け替え
3. おわり

ポインタをごにょる関係上、unsafeだったりします。
この例では、二つのメソッドの処理を入れ替えていますが、これは絶対ではなく、ポインタを共有する事もできます。
iOS実機でも動いたのが、わりと衝撃でした。

ただし、一度でも実際に呼び出したメソッドは、変更不能

これが致命傷...

using UnityEngine;
using System;
using System.Collections;
using System.Reflection;

public class ReplaceMethod1 : MonoBehaviour
{
    static void ExchangeFunctionPointer(MethodInfo method0, MethodInfo method1)
    {
        unsafe
        {
            var functionPointer0 = method0.MethodHandle.Value.ToPointer();
            var functionPointer1 = method1.MethodHandle.Value.ToPointer();
            var tmpPointer = *((int*)new IntPtr(((int*)functionPointer0 + 1)).ToPointer());
            *((int*)new IntPtr(((int*)functionPointer0 + 1)).ToPointer()) = *((int*)new IntPtr(((int*)functionPointer1 + 1)).ToPointer());
            *((int*)new IntPtr(((int*)functionPointer1 + 1)).ToPointer()) = tmpPointer;
        }
    }

    void OnGUI()
    {
        if (GUILayout.Button("This is OnGUIv1"))
        {
            MethodInfo onGuiMethod = this.GetType().GetMethod("OnGUI", BindingFlags.NonPublic | BindingFlags.Instance);
            MethodInfo onGuiV2Method = this.GetType().GetMethod("OnGUIv2", BindingFlags.NonPublic | BindingFlags.Instance);
            ExchangeFunctionPointer(onGuiMethod, onGuiV2Method);        }
    }

    void OnGUIv2()
    {
        if (GUILayout.Button("This is OnGUIv2"))
        {
            MethodInfo onGuiMethod = this.GetType().GetMethod("OnGUI", BindingFlags.NonPublic | BindingFlags.Instance);
            MethodInfo onGuiV2Method = this.GetType().GetMethod("OnGUIv2", BindingFlags.NonPublic | BindingFlags.Instance);
            ExchangeFunctionPointer(onGuiMethod, onGuiV2Method);        }
    }
}

本来は、こうしたかったんですが、どうにも、一度ロードされたメソッドは、後からポインタを付け替えても意味がないようです?

Debug.LogをApplication.RegisterLogCallback以外の方法でフックする用途など

#if UNITY_EDITOR
using UnityEditor;
using System;
using System.Collections;
using System.Reflection;

[InitializeOnLoad]
public class ReplaceMethod2
{
    static ReplaceMethod2()
    {
        MethodInfo logMethod = typeof(Debug).GetMethod("Log", new System.Type[] { typeof(object) });
        MethodInfo logWarningMethod = typeof(Debug).GetMethod("LogWarning", new System.Type[] { typeof(object) });
        ExchangeFunctionPointer(logMethod, logWarningMethod);
        Debug.LogWarning("this is Warning");
        Debug.Log("this is Log");
    }

    static void ExchangeFunctionPointer(MethodInfo method0, MethodInfo method1)
    {
        unsafe
        {
            var functionPointer0 = method0.MethodHandle.Value.ToPointer();
            var functionPointer1 = method1.MethodHandle.Value.ToPointer();
            var tmpPointer = *((int*)new IntPtr(((int*)functionPointer0 + 1)).ToPointer());
            *((int*)new IntPtr(((int*)functionPointer0 + 1)).ToPointer()) = *((int*)new IntPtr(((int*)functionPointer1 + 1)).ToPointer());
            *((int*)new IntPtr(((int*)functionPointer1 + 1)).ToPointer()) = tmpPointer;
        }
    }
}
#endif

てきとーにDebug.LogとDebug.Warningを入れ替えてみてます。
スクリーンショット 2015-01-10 午後10.09.45.png

26
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
21