什么是Cecil
“Cecil是由Jb Evain 开发,用于生成和浏览ECMA CIL 格式的程序和函数库。它完全支持泛型,支持部分调试符号。简单说来,利用Cecil,你可以加载存在的程序集,浏览其中所有的类型,动态的编辑他们,并保存到磁盘上成为一个新的编辑过的程序集”
以上说明来自:
http://www.mono-project.com/Cecil
在这个教程中,我将花一些时间演示在IronPython中使用Cecil的功能。基本上,我就是用Cecil来找到不同的方法,这些方法能从一个方法中被调用。是不是很由意思呀?那么请继续往下看吧。
在继续之前,请下载并安装
IronPython和
Cecil先。你可以不用下载Cecil,因为我已经把它包含在我的这个
Solution文件中了。
首先,我们用C#来创建一个简单的Class Library项目,我们用这个项目生成的程序集用于在后面被Cecil加载。源代码如下(CecilCase.dll 是非常简单的):
//
MainCase.cs
public
class
MainCase
{
public
void
PublicMethod()
{
Console.WriteLine(
"
Hello
"
);
PrivateMethod();
new
SecondCase().Help(
"
Help me
"
);
}
public
void
AddMethod()
{
}
private
void
PrivateMethod()
{
}
public
void
MethodWithArgument(
string
name)
{
Console.WriteLine(name);
}
}
//
SecondCase.cs
class
SecondCase
{
public
void
Help(
string
message)
{
Console.WriteLine(message);
}
}
把这个项目编译成dll,然后打开IronPython的控制台。
保证把IronPython启动目录和CecilCase.dll的输入目录是同一个。也要记得把Mono.Cecil.dll拷贝到同一目录。
我们开始添加CLR的支持,并引用Mono.Cecil dll
>>
import
clr
>>
clr.AddReference(
"
Mono.Cecil
"
)
接着我们要导入所有类型
>>
from
Mono.Cecil
import
*
用dir()命令来看看我们加载了那些类型
>>
dir()
接下来,我们需要把上面编译好的程序集加载进来。根据
FAQ ,我们使用AssemblyFactory类来加载目标程序集。
>>
myAsm
=
AssemblyFactory.GetAssembly(
"
CecilCase.dll
"
)
>>
myAsm
<
Mono.Cecil.AssemblyDefinition object at
0x000000000000002B
[CecilCase, Version
=
1.0
.
0.0
, Culture
=
neutral,
PublicKeyToken
=
null]
>
类型
现在,我们来得到程序集中类型。数量是多少?
>>>
myAsm.MainModule.Types.Count
3
3个类型?我想我只编写了2个啊?让我们看看为什么会这样?
>>>
for
caseType
in
myAsm.MainModule.Types:
print
caseType
<
Module
>
CecilCase.MainCase
CecilCase.SecondCase
So
<
Module
>
was the hidden type. We
'
ll ignore it from now on.
>>>
for
caseType
in
myAsm.MainModule.Types:
if
caseType.Name
!=
'
<Module>
'
:
print
caseType.Name
MainCase
SecondCase
方法
从一个特定的类型得到其中的方法也是很容易。我们从MainCase开始。
>>>
mainCaseType
=
myAsm.MainModule.Types[
1
]
>>>
mainCaseType
<
Mono.Cecil.TypeDefinition object at
0x000000000000002D
[CecilCase.MainCase]
>
记住[0]类型是
<Module>。现在,我们得到了
MainCase的类型定义。
>>>
mainCaseType.Methods.Count
4
>>>
for
met
in
mainCaseType.Methods:
print
met.Name
PublicMethod
AddMethod
PrivateMethod
MethodWithArgument
得到完整的方法定义
>>>
for
met
in
mainCaseType.Methods:
print
met
System.Void CecilCase.MainCase::PublicMethod()
System.Void CecilCase.MainCase::AddMethod()
System.Void CecilCase.MainCase::PrivateMethod()
System.Void CecilCase.MainCase::MethodWithArgument(System.String)
Method Body:
为了看到方法的内部,我们从第一个方法MainCase.PublicMethod()开始
>>>
pMet
=
mainCaseType.Methods[0]
>>>
pMet
<
Mono.Cecil.MethodDefinition object at
0x000000000000002F
[System.Void CecilCase
.MainCase::PublicMethod()]
>
为了看到pMet包含的方法和属性,使用dir()
>>>
dir(pMet)
[
'
Accept
'
,
'
Attributes
'
,
'
Body
'
,
'
CallingConvention
'
,
'
Cctor
'
,
'
Clone
'
,
'
Ctor
'
,
'
CustomAttributes
'
,
'
DeclaringType
'
,
'
Equals
'
,
'
ExplicitThis
'
,
'
Finalize
'
,
'
Gene
ricParameters
'
,
'
GetHashCode
'
,
'
GetSentinel
'
,
'
GetType
'
,
'
HasBody
'
,
'
HasThis
'
,
'
ImplAttributes
'
,
'
IsAbstract
'
,
'
IsConstructor
'
,
'
IsFinal
'
,
'
IsHideBySignature
'
,
'
IsInternalCall
'
,
'
IsNewSlot
'
,
'
IsRuntime
'
,
'
IsRuntimeSpecialName
'
,
'
IsSpecialNa
me
'
,
'
IsStatic
'
,
'
IsVirtual
'
,
'
MakeDynamicType
'
,
'
MemberwiseClone
'
,
'
MetadataTok
en
'
,
'
Name
'
,
'
Overrides
'
,
'
PInvokeInfo
'
,
'
Parameters
'
,
'
RVA
'
,
'
Reduce
'
,
'
Referen
ceEquals
'
,
'
ReturnType
'
,
'
SecurityDeclarations
'
,
'
SemanticsAttributes
'
,
'
This
'
,
'
ToString
'
,
'
__class__
'
,
'
__doc__
'
,
'
__init__
'
,
'
__module__
'
,
'
__new__
'
,
'
__redu
ce__
'
,
'
__reduce_ex__
'
,
'
__repr__
'
,
'
__str__
'
]
这里,我们感兴趣的是Body
>>>
pMet.Body
<
MethodBody object at
0x0000000000000034
>
>>>
dir(pMet.Body)
[
'
Accept
'
,
'
CilWorker
'
,
'
CodeSize
'
,
'
Equals
'
,
'
ExceptionHandlers
'
,
'
Finalize
'
,
'
GetHashCode
'
,
'
GetType
'
,
'
InitLocals
'
,
'
Instructions
'
,
'
MakeDynamicType
'
,
'
MaxSt
ack
'
,
'
MemberwiseClone
'
,
'
Method
'
,
'
Reduce
'
,
'
ReferenceEquals
'
,
'
Scopes
'
,
'
Simpl
ify
'
,
'
ToString
'
,
'
Variables
'
,
'
__class__
'
,
'
__doc__
'
,
'
__init__
'
,
'
__module__
'
,
'
__new__
'
,
'
__reduce__
'
,
'
__reduce_ex__
'
,
'
__repr__
'
]
现在我们来看看pMet.Body中的
Instructions(译者注:Instructions就是IL语句的序列,它是一个集合对象,我们需要枚举它。
>>>
for
ins
in
pMet.Body.Instructions:
print
ins
Mono.Cecil.Cil.Instruction
Mono.Cecil.Cil.Instruction
Mono.Cecil.Cil.Instruction
Mono.Cecil.Cil.Instruction
Mono.Cecil.Cil.Instruction
Mono.Cecil.Cil.Instruction
Mono.Cecil.Cil.Instruction
Mono.Cecil.Cil.Instruction
Mono.Cecil.Cil.Instruction
Mono.Cecil.Cil.Instruction
Mono.Cecil.Cil.Instruction
Mono.Cecil.Cil.Instruction
好,它显示出了我们期望的结果。如果你尝试看看ins是否具有智能感知功能的话,目前是无法正常工作的。所以,我们需要添加从Cecil命名空间中添加所有类型。
>>>
from
Mono.Cecil.Cil
import
*
>>>
for
ins
in
pMet.Body.Instructions:
print
ins.OpCode.Name
nop
ldstr
call
nop
ldarg.0
call
nop
newobj
ldstr
call
nop
ret
我们来和Reflector的反编译输出结果比较一下:
很酷吧!基本使用我们介绍到这里。接下来,我们将开始关注3个“call”语句(译者注:call语句指的是上图所显示出的IL语句)。
>>>
for
ins
in
pMet.Body.Instructions:
if
ins.OpCode.Name
==
'
call
'
:
ins.Operand
<
Mono.Cecil.MethodReference object at
0x000000000000003F
[System.Void System.Console::WriteLine(System.String)]
>
<
Mono.Cecil.MethodDefinition object at
0x0000000000000031
[System.Void CecilCase.MainCase::PrivateMethod()]
>
<
Mono.Cecil.MethodDefinition object at
0x000000000000003D
[System.Void CecilCase.SecondCase::Help(System.String)]
>
以上得到了Operand的细节。现在,我们需要得到这方法的定义对象以便我们能递归的浏览每一个子方法。为了达到目的,我们定义了一个方法,它能把传入的MethodDefinition的对象递归浏览,并打印出子call方法。
>>>
def
parseMethodBody(methodDef):
for
ins
in
methodDef.Body.Instructions:
if
ins.OpCode.Name
==
'
call
'
:
print
ins.Operand
从上面可知,ins.Operand返回了MethodDefinition/MethodReference。我们还不知道MethodDefinition和MethodReference之间的区别。
>>>
for
ins
in
pMet.Body.Instructions:
if
ins.OpCode.Name
==
'
call
'
:
parseMethodBody(ins.Operand)
Traceback (most recent call last):
File , line 0,
in
<
stdin
>
#
#168
File , line 0,
in
parseMethodBody
AttributeError:
'
MethodReference
'
object has no attribute
'
Body
'
好,现在我们知道了MethodReference不具有Body,所以我们可以忽略它,这样需要修改一下parseMethodBody方法。
>>>
def
parseMethodBody(mDef):
if
isinstance(mDef, MethodDefinition):
for
ins
in
mDef.Body.Instructions:
if
ins.OpCode.Name
==
'
call
'
:
print
ins.Operand
>>>
for
ins
in
pMet.Body.Instructions:
if
ins.OpCode.Name
==
'
call
'
:
parseMethodBody(ins.Operand)
通过运行这个代码,我们不能得到任何结果,因为我一开始并没有(有点傻的)在SecondCase.Help()添加需要的代码。现在,我们来添加之。
//
SecondCase.cs after adding another method
class
SecondCase
{
public
void
Help(
string
message)
{
Console.WriteLine(message);
HelpMeToo(message);
}
public
void
HelpMeToo(
string
message2)
{
Console.WriteLine(message2);
}
}
我们需要用AssemblyFactory 重新添加程序集来看看刚刚我们添加的
HelpMeToo方法。让我们扼要重述一下上面做个的一些步骤,看看你记得多少:
- 在添加Mono.Cecil的引用之后用AssemblyFactory来加载程序集
- 得到需要的类型
- 得到需要的方法
- 得到Instructions(只需要得到“call”)
- 得到每一个“call”的Operand信息
我们继续:
>>>
for
inst
in
pMet.Body.Instructions:
if
inst.OpCode.Name
==
'
call
'
:
parseMethodBody(inst.Operand)
System.Void System.Console::WriteLine(System.String)
System.Void CecilCase.SecondCase::HelpMeToo(System.String)
你能看到我们得到了一个从
SecondCase::Help() 中调用方法的列表
类似的,我们能开发一个递归函数来枚举这些方法。但是,我觉得现在对于第一个教程来说已经足够多。需要我还会准备下一个练习。
(译者注:最后没有再翻译作者的一些口水话了)
=========================
原文地址:http://weblogs.asp.net/nleghari/pages/ironpythoncecil.aspx
为什么我要翻译这篇文章:
一直以来,我一直希望让IronPython能运行在Smart Device上,但可惜IronPython内部用到的.NET的动态程序集生成机制在.NET CF中不支持,所以IronPython也无法成功在.NET CF中运行。
但是,Cecil是一个完全可以在.NET CF中运行的函数库。所以,我曾经在IronPython的邮件列表中提到过,可以在IronPython底层使用Cecil来生成动态程序集,这样就可以运行于.NET CF下了。
当然,让IronPython运行于.NET CF下最好的方法是,让.NET CF也具有动态生成程序集的功能。
这是一个我翻译本文的原因。另外Cecil是非常好的一个函数库,也借此机会介绍给大家。
作者关于这个话题,还写了
PartII,和
PartIII。我以后有时间再继续翻译Part2和Part3了。
这些文章其中还随带介绍了如下组件:
GLEE ,GLEE是微软研究院开发的一个做图的组件
Netron Graph,Netron Graph是一个著名的开源做图组件
作者关于GLEE的文章:
Fun With Ironpython GLEE