Mono.Cecil使用示例之给UnityEditor.dll中的ConsoleWindow添加双击委托

该示例将在上例的基础上给 UnityEditor.dll 中的 ConsoleWindow 添加双击委托
Unity Console 窗口是查看日志的窗口,我们希望在 Console 窗口中双击某条日志时获得通知,该窗口对应的实现代码在 UnityEditor.dll 中的 ConsoleWindow
使用 ILSpy 反编译 UnityEditor.dll 中的 ConsoleWindow 类,可以看到其双击判断在 OnGUI 函数中,并在双击时调用了 LogEntries. RowGotDoubleClicked 函数。
我们可以使用 Mono.Cecil ConsoleWindow 类添加一个静态委托
public static Action< int > onDoubleClick;
添加一个委托执行函数
void OnDoubleClick ( int row)
{
    
if (ConsoleWindow.onDoubleClick != null)
    {
        ConsoleWindow.
onDoubleClick (row);
    }
}
并在 OnGUI 函数中调用 LogEntries. RowGotDoubleClicked 函数的后面调用 OnDoubleClick 函数

实现步骤
  1. 读取 UnityEditor.dll 程序集
在上例解决保存报错的基础上封装一个读取程序集的函数
public static AssemblyDefinition GetAssemblyDefination ( string assemblyPath )
    {
        AssemblyDefinition ad = AssemblyDefinition . ReadAssembly ( assemblyPath );
DefaultAssemblyResolver dar = ad . MainModule . AssemblyResolver as DefaultAssemblyResolver ;
        if ( dar != null )
        {
            dar . AddSearchDirectory ( Path . GetDirectoryName ( assemblyPath ));
        }
        return ad ;
}
          则读取 UnityEditor.dll 程序集的代码如下
          string unityEditorPath = typeof ( AssetDatabase ). Module . FullyQualifiedName ;
     AssemblyDefinition ad = GetAssemblyDefination ( unityEditorPath );
  1. 查找ConsoleWindow类
var type = ad . MainModule . Types . First ( t => t . Name == "ConsoleWindow" );
  1. ConsoleWindow 类添加一个类型为 Action < int > 的静态公有委托 onDoubleClick
var action = new FieldDefinition ( "onDoubleClick" , Mono . Cecil . FieldAttributes . Public | Mono . Cecil . FieldAttributes . Static , ad . MainModule . Import ( typeof ( Action < int >)));
type . Fields . Add ( action );
  1. ConsoleWindow 类添加一个无返回值一个 int 类型形参 row 的函数 OnDoubleClick
var method = new MethodDefinition ( "OnDoubleClick" , Mono . Cecil . MethodAttributes . Public , ad . MainModule . TypeSystem . Void );
method . Parameters . Add ( new ParameterDefinition ( "row" , Mono . Cecil . ParameterAttributes . None , ad . MainModule . TypeSystem . Int32 ));
type . Methods . Add ( method );
  1. OnDoubleClick函数添加函数体
var ilProcessor = method . Body . GetILProcessor ();
var il1 = ilProcessor . Create ( OpCodes . Ldsfld , action );
var il2 = ilProcessor . Create ( OpCodes . Callvirt , ad . MainModule . Import ( typeof ( Action < int >). GetMethod ( "Invoke" , BindingFlags . Instance | BindingFlags . Public )));
method . Body . Instructions . Add ( il1 );
method . Body . Instructions . Add ( il1 );
method . Body . Instructions . Add ( ilProcessor . Create ( OpCodes . Ldarg_1 ));
method . Body . Instructions . Add ( il2 );
method . Body . Instructions . Add ( ilProcessor . Create ( OpCodes . Ret ));
ilProcessor . InsertAfter ( method . Body . Instructions [1], ilProcessor . Create ( OpCodes . Brfalse , method . Body . Instructions . Last ()));
  1. OnGUI函数中调用LogEntries.RowGotDoubleClicked函数的后面添加对OnDoubleClick函数的调用
var ongui = type . Methods . First (m => m. Name == "OnGUI" );
    var onguiProcess = ongui . Body . GetILProcessor ();
    for ( int i = 0; i < ongui . Body . Instructions . Count ; i ++)
    {
        if ( ongui . Body . Instructions [ i ]. ToString (). Contains ( "RowGotDoubleClicked" ))
        {
            onguiProcess . InsertAfter ( ongui . Body . Instructions [ i ], onguiProcess . Create ( OpCodes . Call , method ));
            onguiProcess . InsertAfter ( ongui . Body . Instructions [ i ], ongui . Body . Instructions [ i - 1]);
            onguiProcess . InsertAfter ( ongui . Body . Instructions [ i ], ongui . Body . Instructions [ i - 2]);
            onguiProcess . InsertAfter ( ongui . Body . Instructions [ i ], ongui . Body . Instructions [ i - 3]);
            onguiProcess . InsertAfter ( ongui . Body . Instructions [ i ], ongui . Body . Instructions [ i - 3]);
            i += 5;
        }
}
  1. 保存修改后的程序集
ad . Write ( Application . dataPath + "/../UnityEditor.dll" );
其中第 5, 6 步骤中对 IL 代码的操作需要对 C# 编译为对应的 IL 代码非常熟悉,否则插错一条 IL 代码可能使整个程序集无法编译通过甚至使整个程序崩溃。但是 如何保证5,6步骤中插入的IL代码的正确性呢
一般 IL 代码都是由 .Net 系的编程语言编译生成的,只要编译通过, IL 代码一般不会发生错误,所以要保证对 IL 代码操作的正确性,也需要借助到编译器的编译
上述第 5 ,第 6 步骤对 IL 代码的操作可以通过下面的步骤保证其正确性
  1. 保存反编译出来的 ConsoleWindow
  2. 将保存的类导进 Unity 中,修改使其可以通过编译
  3. 为修改后的类添加委托。委托执行函数,修改 OnGUI 函数等等
  4. 在工程目录 \Library\ScriptAssemblies\ 下找到 Assembly-CSharp-Editor.dll 文件,在 ILSpy 中打开,反编译其中修改过的 ConsoleWindow 类为 IL 代码
  5. 反编译 UnityEditor.dll 中的 ConsoleWindow 类为 IL 代码,和第 4 步骤中的 IL 代码对比,就可以在关键部位逐条插入多出来的 IL 代码,或者直接替换对应的 IL 代码
    如果在 ILSpy 中可以成功反编译修改后的程序集为 C# 代码,说明对 IL 代码的修改操作成功了

与上个示例一样,关掉 Unity vs ,用修改后的 UnityEditor 替换工程目录 \Library\UnityAssemblies\UnityEditor.dll Unity 安装目录 \Editor\Data\Managed\UnityEditor.dll
再打开工程就会发现可以在代码中注册 ConsoleWindow. onDoubleClick 委托了

使用示例
public class ConsoleActionTest
{
    [ InitializeOnLoadMethod ]
    static void Init ()
    {
        ConsoleWindow . onDoubleClick -= OnConsoleDoubleClick ;
        ConsoleWindow . onDoubleClick += OnConsoleDoubleClick ;
    }
    static void OnConsoleDoubleClick ( int row )
    {
        LogEntry log = new LogEntry ();
        LogEntries .GetEntryInternal( row , log );
        Debug . Log ( log . file );
        Debug . Log ( log . line );
    }
}

你可能感兴趣的:(unity工作日记)