基于Fody的IL编织器

前面一篇文章介绍了Source Generator的基本用法,本文结合实例再对Fody编织器的创建进行介绍。

值得注意的是,Source Generator生成代码的方式更加简单,而Fody必须在IL中间代码的基础上进行修改,会更加繁琐,本文只关注如何创建编织器,重点不在编制IL中间代码的语法上。

需求描述

本文实例很简单,当为一个方法添加[HelloWorldAddin]注解时,就在该方法执行之前输出Hello World.

创建编织器项目

  • 创建一个netstandard2.0类库项目,添加必要的NuGet包:

  • 创建一个继承自BaseModuleWeaver的编织器,实现核心逻辑函数Execute()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
using Fody;
using Mono.Cecil.Cil;
using Mono.Cecil;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace HelloWorldAddin.Fody {
public class ModuleWeaver : BaseModuleWeaver {
public override void Execute() {
foreach (var type in ModuleDefinition.Types) {
foreach (var method in type.Methods) {
var customerAttribute = method.CustomAttributes.FirstOrDefault(x => x.AttributeType.Name == nameof(HelloWorldAddinAttribute));
if (customerAttribute != null) {
ProcessMethod(method);
}
}
}
}

public override IEnumerable<string> GetAssembliesForScanning() {
yield return "mscorlib";
yield return "System";
}

private MethodInfo _writeLineMethod => typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) });

private void ProcessMethod(MethodDefinition method) {
// 获取当前方法体中的第一个 IL 指令
var processor = method.Body.GetILProcessor();
var current = method.Body.Instructions.First();

// 插入一个 Nop 指令,表示什么都不做
var first = Instruction.Create(OpCodes.Nop);
processor.InsertBefore(current, first);
current = first;

// 构造 Console.WriteLine("Hello World")
foreach (var instruction in GetInstructions(method)) {
processor.InsertAfter(current, instruction);
current = instruction;
}
}

private IEnumerable<Instruction> GetInstructions(MethodDefinition method) {
yield return Instruction.Create(OpCodes.Nop);
yield return Instruction.Create(OpCodes.Ldstr, "Hello World.");
yield return Instruction.Create(OpCodes.Call, ModuleDefinition.ImportReference(_writeLineMethod));
}
}
}

创建注解定义库

1
2
3
4
5
6
using System;

namespace HelloWorldAddin {
[AttributeUsage(AttributeTargets.Method)]
public class HelloWorldAddinAttribute : Attribute { }
}

创建测试项目

  • 在项目配置文件中引起编织器dll
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net48</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Fody" Version="6.9.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\HelloWorldAddin\HelloWorldAddin.csproj" />
<WeaverFiles Include="$(SolutionDir)HelloWorldAddin.Fody\bin\$(Configuration)\netstandard2.0\HelloWorldAddin.Fody.dll" />
</ItemGroup>
</Project>
  • FodyWeavers.xml中引入编织器
1
2
3
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd" >
<HelloWorldAddin/>
</Weavers>
  • 编写源代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
using HelloWorldAddin;

namespace TestHelloWorldAddin {
internal class Program {
static void Main(string[] args) {
Echo();
}

[HelloWorldAddin]
public static void Echo() {
Console.WriteLine("Hello Fody.");
}
}
}
  • 成功:

(转载本站文章请注明作者和出处lihaohello.top,请勿用于任何商业用途)

评论