该示例将在上例的基础上给
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
函数
实现步骤
-
读取
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
);
-
查找ConsoleWindow类
var
type
=
ad
.
MainModule
.
Types
.
First
(
t
=>
t
.
Name
==
"ConsoleWindow"
);
-
给
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
);
-
给
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
);
- 给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
()));
- 在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;
}
}
- 保存修改后的程序集
ad
.
Write
(
Application
.
dataPath
+
"/../UnityEditor.dll"
);
其中第
5,
第
6
步骤中对
IL
代码的操作需要对
C#
编译为对应的
IL
代码非常熟悉,否则插错一条
IL
代码可能使整个程序集无法编译通过甚至使整个程序崩溃。但是
如何保证5,6步骤中插入的IL代码的正确性呢
一般
IL
代码都是由
.Net
系的编程语言编译生成的,只要编译通过,
IL
代码一般不会发生错误,所以要保证对
IL
代码操作的正确性,也需要借助到编译器的编译
上述第
5
,第
6
步骤对
IL
代码的操作可以通过下面的步骤保证其正确性
-
保存反编译出来的
ConsoleWindow
类
-
将保存的类导进
Unity
中,修改使其可以通过编译
-
为修改后的类添加委托。委托执行函数,修改
OnGUI
函数等等
-
在工程目录
\Library\ScriptAssemblies\
下找到
Assembly-CSharp-Editor.dll
文件,在
ILSpy
中打开,反编译其中修改过的
ConsoleWindow
类为
IL
代码
-
反编译
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
);
}
}