建立一个解决方案,里面添加2个类库项目FirstPSPlugin和FirstPlugin.Task。
一般一个Postsharp由两个部分构成,FirstPSPlugin这个程序集中定义一系列Custom Attributes,用于标记将要被
处理的语言元素。例如可以标记一个OnExceptionAttribute到一个方法中,只是这个方法将会在post-process阶段
被处理。总之这一部分中定义的类型完全是用来做标记的。我们使用插件的时候将会使用这些类标记我们代码的特
定位置。
第二部分是用来处理这些标记的,实现代码编织。安装完Postsharp后,Postsharp会自动和msbuild关联起来。当使
用msbuild编译程序集的时候,postsharp会检查正在编译的程序集的引用。如果引用了Postsharp.public这个程序
集,就会在常规编译结束后调用Postsharp对生成的程序集作进一步转换,例如修改方法中的il。
这两个项目都需要引用Postsharp.Public和Postsharp.Core。
先来看看FirstPSPlugin的实现。很简单,我们只需要1个类即可:
using System;
using PostSharp.Extensibility;
namespace MyPostSharp
{
[Serializable]
[AttributeUsage(AttributeTargets.Method)]
public class MyMethodAttribute : Attribute, IRequirePostSharp
{
public PostSharpRequirements GetPostSharpRequirements()
{
var requirements = new PostSharpRequirements();
requirements.PlugIns.Add("FirstPlugin.Task");
requirements.Tasks.Add("FirstPlugin.Task.MyMethod");
requirements.VariesWithInstance = false;
return requirements;
}
}
}
注意FirstPSPlugin必须要强命名,否则Postsharp会报错误。
IRequirePostSharp接口只有一个方法,这个方法要求返回一个PostSharpRequirements类型的参数。在返回的实例
当中指明了处理这个Attribute所需要的Postsharp插件和Postsharp插件中具体用来处理这个Attribute的任务。
好,FirstPSPlugin已经完成了,可以Build之。
FirstPlugin.Task这个项目中只需要1个类即可,这个类实现了IL编织的具体过程:
namespace FirstPlugin.Task
{
public class FirstPSTask : PostSharp.Extensibility.Task
{
public override bool Execute()
{
return base.Execute();
}
}
}
好,这个类我们先这样放着。
前面提到了2个概念,插件和任务。这里我们说的插件就是指FirstPlugin.Task这个程序集。那么什么是任务?
任务就是Build过程中必须执行的一项操作,通过一个xml文件定义。在FirstPlugin.Task中添加一个xml文
件,用psplugin做扩展名,输入以下内容:
<?xml version="1.0" encoding="utf-8" ?>
<PlugIn xmlns="http://schemas.postsharp.org/1.0/configuration">
<TaskType Name="FirstPlugin.Task.MyMethod" Implementation="FirstPlugin.Task.FirstPSTask,
FirstPlugin.Task" Phase="Transform">
</TaskType>
</PlugIn>
这个xml文件在FirstPlugin.Task这个插件中定义了一项任务,这个任务的名称“FirstPlugin.Task.MyMethod”就是
在前面那个自定义特性中所指定的任务名。这样任务就和自定义特性关联起来了。
在项目属性中把FirstPlugin.Task的输出路径改为“C:\Program Files\PostSharp 1.0\PlugIns\”,也Build之。这个
路径是PostSharp插件的公共路径,Postsharp在需要查找插件的时候会找这个路径。
必须把这个文件的属性“copy to output dir”设置为always。
虽然这个时候这个插件什么也不做,但是我们可以先在FirstPlugin.Task.FirstPSTask这个类的Execute方法上加一
个断点以观察其执行时机。
新建一个控制台解决方案FirstPSPlugin.Test。为了后面调试方便最好是新开一个vs建解决方案而不要把这个测试
项目添加到插件的那个解决方案中。
添加对PostSharp.Public和FirstPSPlugin的引用。
也只需要一个类:
using System;
namespace FirstPSPlugin.Test
{
class Program
{
static void Main(string[] args)
{
Test();
}
[FirstPSPlugin.MyMethod]
static void Test()
{
Console.WriteLine("void Program::Test()");
}
}
}
Build之。
好,现在没有任何激动人心的事发生。我们打开vs命令行,切换到FirstPSPlugin.Test项目目录下。
输入:msbuild FirstPSPlugin.Test.csproj /T:Rebuild /P:PostSharpAttachDebugger=True
好,出现了一个未处理异常。用打开FirstPlugin.Task的那个vs去调试这个异常。然后按F5就进入了我们在Execute
方法上的断点。这说明在Build FirstPSPlugin.Test的过程中调用了这个方法。
后面要做的事就是完成Execute方法,使用Postsharp提供的Code Model修改IL。这一部分我还没研究明白。
为了直观起见,写一个小例子:
using System;
using PostSharp.CodeModel;
namespace FirstPlugin.Task
{
public class FirstPSTask : PostSharp.Extensibility.Task
{
public override bool Execute()
{
ModuleDeclaration md = this.Project.Module;
foreach (var t in md.Types)
{
foreach (var m in t.Methods)
{
foreach (var a in m.CustomAttributes)
{
if (a.Constructor.DeclaringType.ToString() ==
"[FirstPSPlugin]FirstPSPlugin.MyMethodAttribute")
{
using (InstructionWriter iw = new InstructionWriter())
{
InstructionSequence ins = m.MethodBody.CreateInstructionSequence();
m.MethodBody.RootInstructionBlock.AddInstructionSequence(ins,
PostSharp.Collections.NodePosition.Before, null);
iw.AttachInstructionSequence(ins);
iw.EmitInstruction(OpCodeNumber.Nop);
iw.EmitInstruction(OpCodeNumber.Nop);
iw.EmitInstruction(OpCodeNumber.Nop);
iw.EmitInstruction(OpCodeNumber.Nop);
iw.EmitInstruction(OpCodeNumber.Nop);
var mscorlib = this.Project.Module.FindMscorlib() as
AssemblyRefDeclaration;
var console = mscorlib.FindType("System.Console",
BindingOptions.Default) as TypeRefDeclaration;
foreach (var mr in console.MethodRefs.GetByName("WriteLine"))
{ //find Console.WriteLine(string) method ref
if (mr.Signature.ParameterTypes.Count == 1 &&
mr.Signature.ParameterTypes[0].ToString() == "string")
{
iw.EmitInstructionString(OpCodeNumber.Ldstr, "Hello
Postsharp!");
iw.EmitInstructionMethod(OpCodeNumber.Call, mr);
break;
}
}
m.CustomAttributes.Remove(a);
iw.DetachInstructionSequence(true);
break;
}
}
}
}
}
return base.Execute();
}
}
}
现在FirstPSTask的Execute方法有了新的实现。好,重新编译测试项目,然后用ILDASM查看Program.Test方法:
.method private hidebysig static void Test() cil managed
{
.maxstack 8
L_0000: nop
L_0001: nop
L_0002: nop
L_0003: nop
L_0004: nop
L_0005: ldstr "Hello Postsharp!"
L_000a: call void [mscorlib]System.Console::WriteLine(string)
L_000f: nop
L_0010: ldstr "void Program::Test()"
L_0015: call void [mscorlib]System.Console::WriteLine(string)
L_001a: nop
L_001b: ret
}
可以看到我们的的IL指令成功地注入了。Good, goooooood。