现在的位置: 首页 > 综合 > 正文

第一个Postsharp插件

2013年04月04日 ⁄ 综合 ⁄ 共 5774字 ⁄ 字号 评论关闭

代码

建立一个解决方案,里面添加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。

 

 

抱歉!评论已关闭.