当前位置: 首页 > 工具软件 > Cecil > 使用案例 >

Mono.Cecil说明文档翻译

夹谷苗宣
2023-12-01

文章原文来自:https://github.com/jbevain/cecil/wiki

翻译:Jeffrey Chou

Mono.Cecil说明文档翻译

目录

主页

编码约定(Coding Conventions)

编译(Compilation)

调试符号(Debug Symbols)

一些问答(FAQ)

如何使用(How To)

1.打开一个程序模块并且打印它的公开成员类型

2.检查一个类型是不是有一些用户自定义的特性

3.在另一个指令之前插入一个(新的)IL指令

输入(Importing)

许可证(License)

版本迁移(Migration)

Mono.Cecil.Rocks程序集

解析(Resolving)

强名称(StrongName)


主页

 

欢迎来到Cecil的维基!

Cecil是一个用来生成和检查“ECMA CIL”格式的程序和类库的一个类库。它全面地支持泛型,支持“pdb和mdb调试符号(Debugging Symbols)”格式,并且它还被广泛的运用。

只需要懂简单的英语,使用Cecil,你就可以载入现存的托管程序集,浏览并检查所有包含的类型,并且立即修改他们,最后保存修改后的程序集。

你可以阅读问答(FAQ)部分,如果你还是有任何的问题,请在mono-cecil的邮寄名单上提交上报这些问题。

如果你想要编译Cecil,有可以阅读编译(Compilation)指导这一小节,并且如果你想要为此出力,你可以阅读编码约定(Coding Conventions)有关的章节。如果你将要一直使用了Cecil 6.0的代码程序,你就应该阅读有关移植(Migration)章节的指导。

通过Nuget包管理器来获得Mono.Cecil,是一个简单从你的项目中去使用Cecil方式。


编码约定(Coding Conventions

 

Cecil的编码遵循Mono编码指南,但是有一些不同。我们仅仅在公开的API中使用了“驼峰式(camelCase)”的公开的参数命名规则。除此之外,我们使用了under_score_case(这他妈是什么意思)的命名规则的参数。整个代码库(仓库repository)也使用了“UNIX”的风格的行结尾(\n)。

请在提供补丁程序时,请遵循编码约定,因为它会使得使审查和集成变得更加容易。


编译(Compilation

 

Cecil尽管是用C# 3写的,但是它可以在各种版本的.net framework的上使用。你可以使用各种.net IDE,例如Visual Studio 2010,SharpDevelop,MonoDevelop,或者使用Microsoft的msbuild或者Mono的xbuild工具来编译Cecil。

 

csproj文件定义了一串不同的配置,例如:

● net_2_0_Debug, net_2_0_Release

● net_3_5_Debug, net_3_5_Release

● net_4_0_Debug, net_4_0_Release

● silverlight_Debug, silverlight_Release

● winphone_Debug, winphone_Release

 

如果你需要调整编译过程和方式去适应你的需要,下面的符号需要依赖你目标的.net得版本来被定义:

● NET_3_5

● NET_4_0

● SILVERLIGHT

● CF

 

你也可以调整csproj去定义一个READ_ONLY的符号。对此,只有Cecil只读的层面才会被编译,这可以对应需要处理大小的特殊情况。


调试符号(Debug Symbols

 

Cecil用来支持调试符号的内容,存在于下面两个程序集中:Mono.Cecil.Pdb.dll和Mono.Cecil.Mdb.dll。它们是用来读取和写入(操作)调试符号保存文件的,对于.net是pdb文件和Mono是mdb文件。

 

● Mono.Cecil.Mdb是一个工作在.net和Mono下面的任何平台(x86、x64和AnyCPU)的完全的托管程序集;

● Mono.Cecil.Pdb有一个托管的pdb读取器(reader),但是它的写入器(writer)使用的是COM互操作。这意味着你可以使用它读取任何地方的pdb符号,但是你需要在windows上运行程序来发布相关的pdb文件。托管的读取器使用来自CCI项目的代码。

 

从API的角度看,有两种与调试符号交互的方法。这最简单的方法就是简单的将’True’初始化给ReaderParameters.ReadSymbols对象 或者 WriterParameters.WriteSymbols对象。Cecil将会根据运行时(在.net上是pdb,在Moon上是mdb)尝试去载入相对应调试保存支持程序集,并在此基础上读取和写入适当的符号。

 

下面的例子展示了如何去读取和写入一个符号文件将要被重写的程序集。

var readerParameters = new ReaderParameters { ReadSymbols = true };
var assemblyDefinition = AssemblyDefinition.ReadAssembly (fileName, readerParameters);

// make required changes.
var writerParameters = new WriterParameters { WriteSymbols = true };
assemblyDefinition.Write (outputFile, writerParameters);

如果你需要更多的控制,你可以指定eaderParameters.SymbolReaderProvider对象和WriterParameters.SymbolWriterProvider对象。这些接口分别的控制指定模块的一个Mono.Cecil.Cil.ISymbolReader对象和一个Mono.Cecil.Cil.ISymbolWriter对象的创建。


一些问答(FAQ

 

1.我可以使用Cecil用于我的非开源专有的项目中使用吗?

简答回答:可以使用。你可能阅读Cecil的许可证的部分章节。

2.CecilMono的一部分,那么我可以在.net上使用它吗?

是的,确实可以使用。

3.Cecil支持.net 4.0吗?

无条件的支持。

4.我可以用Cecil.net 4.0版本来操纵在一个.net 2.0的程序集中吗?

可以,Cecil是独立于运行时存在的。你可以在.net 4.0的环境中书写或者载入.net 2.0的程序集,或者反过来。

5.Cecil可以在Silverlight/Moonlight上使用和工作吗?

在Silverlight上使用Mono.Cecil,你仅仅只要去编译它,并且在编译的时候指定“SILVERLIGHT”的编译符号就行了。所除了一个他不能使用强名称程序集的特性之外,所有的都可以正常工作。

6.Cecil可以在轻量级框架(the Compact Framework)中使用吗?

为了使Mono.Cecil能在轻量级框架中使用,你仅仅需要编译它,并且在编译的时候指定“CF”编译符号就行了。和上面的第五点一样,你将不能使用强名称程序集。

7.Cecil支持PE32+的程序集吗?

Cecil已经支持AMD64和IA64的特殊的程序集。只需将模块的架构属性设置为适当的目标架构枚举成员即可。

8.Cecil支持混合模式的程序集(mixed mode assemblies)吗?

Cecil可以读取混合模式的程序集,但是不支持编写混合模式的程序集。


如何使用(How To
 

目录

1.打开一个程序模块并且打印它的公开成员类型

2.检查一个类型是不是有一些用户自定义的特性

3.在另一个指令之前插入一个(新的)IL指令

 

1.打开一个程序模块并且打印它的公开成员类型

public void PrintTypes (string fileName)
{
    ModuleDefinition module = ModuleDefinition.ReadModule (fileName);
    foreach (TypeDefinition type in module.Types) {
        if (!type.IsPublic)
            continue;
        Console.WriteLine (type.FullName);
    }
}

2.检查一个类型是不是有一些用户自定义的特性

public static bool TryGetCustomAttribute (TypeDefinition type,
    string attributeType, out CustomAttribute result)
{
    result = null;
    if (!type.HasCustomAttributes)
        return false;

    foreach (CustomAttribute attribute in type.CustomAttributes) {
        if (attribute.AttributeType.FullName != attributeType)
            continue;
 
        result = attribute;
        return true;
    }
 
    return false;
}

定义一个类型如下:

[Foo.Ignore ("Not working yet")]
public class Fixture {
}

调用代码:

public static string GetIgnoreReason (TypeDefinition type)
{
    CustomAttribute ignoreAttribute;
    if (!TryGetCustomAttribute (type, "Foo.IgnoreAttribute", out ignoreAttribute))
        return string.Empty;

   if (ignoreAttribute.ConstructorArguments.Count != 1)
        return string.Empty;

    return (string) ignoreAttribute.ConstructorArguments [0].Value;
}

3.在另一个指令之前插入一个(新的)IL指令

下面的例子将会作为在方法体中的第一个指令,在一个方法的引用中插入一个方法的进行调用。

var processor = method.Body.GetILProcessor();
var newInstruction = processor.Create(OpCodes.Call, someMethodReference);
var firstInstruction = method.Body.Instructions[0];
                 
processor.InsertBefore(firstInstruction, newInstruction);

输入(Importing

 

当你修改一个程序集的时候,你往往希望去注入另一个程序集的方法调用。例如,当你注入日志记录代码,你会希望增加调用做日志的程序集的调用代码。我们把创建程序集外部的代码的一个引用的过程称之为“输入(importing)”

通过每一个ModuleDefinition类型的定义,引用对象的范围被定义。因此,所有的输入的方法被定义在ModuleDefinition类型的对象中,并且可以在.net运行时的类型系统(System.Type,System.Reflection.MethodBase,System.Reflection.FieldInfo),和Cecil的类型系统(TypeReferenc,MethodReference,FieldReference),这两个系统中进行工作。

 

下面的例子展示了如何在一个ModuleDefinition类型的对象的顶部,去注入的一个Console.WriteLine函数的调用。

using Mono.Cecil;
using Mono.Cecil.Cil;

// ...

var method = GetMethodDefinition (...);
var il = method.Body.GetILProcessor ();

var ldstr = il.Create (OpCodes.Ldstr, method.Name);
var call = il.Create (OpCodes.Call,
         method.Module.Import (
                 typeof (Console).GetMethod ("WriteLine", new [] { typeof (string) })));
 
il.InsertBefore (method.Body.Instructions [0], ldstr);
il.InsertAfter (method.Body.Instructions [0], call);

 

无论何时,输入(Importing)功能在你增加元数据到一个程序集的时候都扮演的重要的作用。下面的例子,让我们对一个模块增加一个有一个简单的静态方法类。

var module = GetModuleDefinition (...);

var type = new TypeDefinition (
         "Foo.Bar",
         "Baz",
         TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.Sealed,
         module.Import (typeof (object)));

module.Types.Add (type);
 
var method = new MethodDefinition (
         "Bang",
         MethodAttributes.Public | MethodAttributes.Static,
         module.Import (typeof (void)));
 
type.Methods.Add (method);

method.Parameters.Add (new ParameterDefinition (module.Import (typeof (string))));

注意到上面的Import被用来获得一个System.Object的TypeReference类型,这个类型对象被用作Foo.Bar.Baz类的基类对象。与此同时,它也被用来设置方法Bang的返回值类型为System.Void,并且对这个方法增加一个类型为System.String的函数参数。


许可证(License

 

Mono.Cecil通过了MIT/X11的许可:

Copyright (c) 2008 - 2011, Jb Evain

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

MIT/X11是一个有效的GPL兼容的许可,并且只要这个许可证随着软件一起发布,便允许在专有的软件中进行使用。


版本迁移(Migration

 

在Mono.Cecil的6.0和9.0版本之间,(Cecil的)Api在几个不同的方向上发展(升级):

● 使得API更加容易的被使用。

● 更好地反映一个程序集的内容。

● 使用.Net 2.0的.Net特性,例如泛型。

 

移植程序到新的Mono.Cecil上是非常容易的。我们建议你使用Cecil(版本的发展)去做一个你的项目的分支,以便可以及时的使你的项目可以在新版本的Mono.Cecil中得到稳定。对于大多数我亲自移植的项目,半天的时候足以让程序可以正常运行,并且可以享受到程序在新版本的Cecil中得到速度提升所带来的快感。

 

一些值得关注的变化如下:

●AssemblyFactory类已经移除。你可以使用ModuleDefinition.ReadModule类型和AssemblyDefinition.ReadAssembly类型的静态方法,并且在他们上条用Write方法来回写他们。

●ModuleDefinition.Types现在仅仅返回最上层(非嵌套)的类型。如果你想要去迭代在一个程序集中所有迭代的类型,那么你可以使用ModuleDefinition.GetTypes()方法。

●TypeDefinition.Constructors类型被合并于TypeDefinition.Methods类型中。这是一个Cecil的想法,并且在它里面打破了在类型中定义方法的顺序。

●ParameterDefinition.Sequence是一个从序数一为参数开始的索引。现在他已经被从序数零开始的ParameterDefinition.Index所替代。为了帮助移植你的项目,你可以替换你对Sequence

属性的使用,而去使用来自扩展方法Mono.Cecil.Rocks.ParameterReferenceRocks.GetSequence

(this ParameterReference self)。

●例如MethodReference或者 MethodDefinition这些实现IMethodSignature.ReturnType方法的这些类型,现在这个方法(MethodReference. ReturnType和MethodDefinition.ReturnType)将会直接返回一个TypeReference。它避免了出现重复的模式:method.ReturnType.ReturnType经常在先前的Cecil代码中被使用。如果你仍然想要去访问用户自定义的特性或者排列一个返回类型上的制定的信息,你可以使用method.MethodReturnType。事实上,method.ReturnType现在是一个对于method.MethodReturnType.ReturnType的快速访问的通道。

●在TypeDefinitionCollection类型上有一个string的索引器属性,它可以被用于通过一个基于全名称的检索来获取一个TypeDefinition的对象。现如今它已经被ModuleDefinition.GetType方法所取代。

●用户自定义特性相关的API变化很大。原来的旧模式在某些时候或者情况下更加的直接,但是并没有对于所有的情况提供足够的信息。大多数使用旧的Cecil的代码的程序看起来像下面代码一样:

(string) attribute.ConstructorParameters [0]

它在现在版本的程序中被

(string) attribute.ConstructorArguments [0].Value

所替代。.ConstructorArgument [0]属性集合中的第一个对象返回一个同时具有TypeReference 类型属性和一个object值属性的CustomAttributeArgument对象

● CilWorker类被移除,并且它被重命名为了ILProcessor。你可以通过在一个MethodBody对象上调用GetILProcessor来获得一个ILProcessor的对象。

● 你现在不得不调用TypeReference.GetElementType和MethodReference.GetElementMethod来代替(以前的)TypeReference.GetOriginalType和MethodReference.GetOriginalMethod。


Mono.Cecil.Rocks程序集

 

Mono.Cecil.Rocks是一个相对于Mono.Cecil来说包含了大多数扩展方法和额外方法的程序集,但是它们对于Mono.Cecil的核心来说并不是很重要。它提供了一个帮助器(类)来迁移新的代码基位(new code base)和新的功能。


解析(Resolving

 

你可以通过传统的反射清除一个引用与一个定义之间不同的方式来操纵.net的类型系统。他仅仅提供给了你解析类型、字段和方法的方法。在元数据的层面,即在Cecil的层面上,我们区别了一个引用与定义(的不同)。

他们(引用与定义)每一个都被限定在了ModuleDefinition对象的范围内。所以,在一个ModuleDefinition中,一个定义意味着一个元数据被定义在了模块自身中,而引用以为了一个元数据被定义在了另外一个模块中。举个实际的例子,如果我们有一个包含有类型Foo.Bar程序集Foo.dll。这个类型Foo.Bar暴露了一个方法返回一个System.String对象的方法Baz。

如果我们使用Cecil载入Foo.dll,那么对于类型Foo.Bar将会是一个TypeDefinition类型的对象,而方法Baz的返回值类型将会是对于System.String的TypeReference类型对象。System.String被定义在程序集mscorlib.dll,这也就是意味着只要你用Cecil来打开mscorlib.dll的程序集,那么System.String将会是一个在里面的TypeDefinition类型。

从一个引用中获得一个定义的过程我们称之为解析(resolving)。在Cecil中所有的可以解析的引用都有一个Resolve的方法。举一个例子,假设你已经在Cecil中载入Foo.dll,并且你想要解析这个Baz方法的返回值的类型:

ModuleDefinition module = ModuleDefinition.ReadModule ("Foo.dll");

TypeDefinition foo_bar = module.Types.First (t => t.FullName == "Foo.Bar");
MethodDefinition baz = foo_bar.Methods.First (m => m.Name == "Baz");

TypeReference string_reference = baz.ReturnType;
 
// resolve into a definition:

TypeDefinition string_definition = string_reference.Resolve ();
ModuleDefinition corlib = string_definition.Module;

在这个例子中,string_definition将会是一个TypeDefinition类型的对象,并且它的模块将会是mscorlib.dll的一个ModuleDefinition类型的对象。为了找到这个定义这个引用的程序集,Cecil使用了ModuleDefinition类型对象的IAssemblyResolver对象来操作Foo.dll。

我们强烈建议你在读取一个ModuleDefinition的时候,指定你的自己的IAssemblyResolver。通过这种方式你可以控制你加载模块和你解析的一些和他的相关的引用的生命周期。

如果你不指定一个用户自定义的解析器,每一个模块将会有他自己的解析器,并且会为此占用更大的内存使用量。


强名称(StrongName

 

用Cecil去修改一个强名称命名的程序集是可能的,再是这个程序集将不能被载入,这个程序集将不得不被重新签名。这里有些不同的选择。

 

● 对于默认状态来说,如果你不指定任何的东西,程序集将会被保存为一个“延迟签名(delay-signed)”的格式中。Cecil将会为强名称的定义去开辟一些空间,但是这并不会被表现出来。所以你不得不使用来自.net sdk的或者是Mono的SN工具来重新对这个程序集进行签名。

sn -R foo.dll

在.net平台,你可以增加程序集到.net运行时无法验证它们的强名称的程序集列表中。这个同样通过SN工具的实现。

sn -Vr foo.dll

● 如果你有访问的密钥对,那么你直接可以要求Cecil去对它进行签名。通过在你写一个模块的时候指定一个System.Reflection.StrongNameKeyPair类对象来进行实现。

var module = ...;

module.Write ("Foo.dll", new WriterParameters {
  StrongNameKeyPair = new StrongNameKeyPair (File.ReadAllBytes ("Foo.snk"))
});

● 你可以决定完全的移除强命名。但这意味着引用你正在修改的每一个程序集都需要更新它的引用。要做到这一点,必须清除它的公钥,并指出它没有签名:

var assembly = ...;
var name = assembly.Name;
 
name.HasPublicKey = false;
name.PublicKey = new byte [0];
module.Attributes &= ~ModuleAttributes.StrongNameSigned;

 

 

 类似资料: