用于.NET的零开销可组合切面技术解析
立即解锁
发布时间: 2025-10-27 00:21:08 阅读量: 6 订阅数: 20 AIGC 

软件工程的本质与实践
### 用于.NET的零开销可组合切面技术解析
#### 1. 切面方法规则
在使用切面方法时,需要遵循以下规则:
- 静态切面方法可用于拦截静态和非静态方法,因为它不能引用目标的`this`。
- 切面方法的参数类型序列必须是目标方法参数类型序列的前缀。这意味着目标方法的参数数量至少要和切面方法相同,且所有参数类型必须匹配。
- 切面方法的返回类型必须与目标方法的返回类型相等。或者,切面方法可以使用泛型返回类型,若目标方法的返回类型是引用类型,也可以使用`Object`。
#### 2. Yiihaw实现概述
Yiihaw使用Cecil字节码操作库实现。选择Cecil是因为它简单高效,且支持合并字节码指令所需的底层操作。
#### 3. 程序集重写
调用Yiihaw时,用户需要指定:
- 一个有效的切入点文件,以文本文件形式存在。
- 一个现有的目标程序集,切面将应用于此。
- 一个现有的切面程序集,包含要引入的建议和其他构造。
生成的编织程序集与目标程序集类型相同(如exe、winexe、库或模块),且完全自包含,不依赖于切面程序集。
#### 4. 处理拦截
编织器一次将建议应用于一个目标方法。如果切入点文件指定,可以将多个建议应用于同一个目标方法,且建议将按指定顺序应用。Yiihaw对建议的应用可视为对目标方法和目标类型(字节码)的转换,这种转换是静态的,在编译后但在加载编译后的字节码之前执行。
下面详细介绍将建议应用于单个目标方法的步骤:
1. **合并目标和建议**:在合并建议和目标方法之前,会创建目标方法所有指令的副本,称为原始主体。如果建议中没有对`Proceed`方法的调用,则会忽略原始目标方法。
2. **局部变量重新编号**:在将原始主体插入目标方法之前,会更新所有对局部变量的引用,确保它们引用正确的局部变量。因为建议方法的局部变量会被添加到原始目标方法的局部变量之前。
3. **处理返回指令**:插入原始主体时,编织器会将所有`ret`(返回)指令替换为`br`(无条件分支)指令,跳转到一个新标签,以保持正确的控制流。例如:
```plaintext
ldarg.1
ldc.i4.5
ble.s
label
ldarg.1
ldc.i4.2
mul
ret
label:
ldarg.1
ret
```
对应的C#源方法为:
```csharp
int M(int x) { if (x > 5) return x * 2; else return x; }
```
假设要应用以下建议:
```plaintext
call
int YIIHAW.API.JoinPointContext::Proceed<int>()
stloc.0
ldstr
"advice"
call
void [mscorlib]System.Console::Write(string)
ldloc.0
ret
```
对应的C#代码为:
```csharp
int Advice() {
int res = JoinPointContext.Proceed<int>();
Console.Write("advice");
return res;
}
```
如果直接替换,会得到错误的编织结果。正确的做法是将`ret`指令替换为`br`指令:
```plaintext
ldarg.1
ldc.i4.5
ble.s
label
ldarg.1
ldc.i4.2
mul
br.s
label2
// <-- replaces ret instruction
label:
ldarg.1
// <-- fallthrough instead of ret
label2: stloc.0
ldstr
"advice"
call
void [mscorlib]System.Console::Write(string)
ldloc.0
ret
```
4. **生成字节码的可验证性**:上述过程会生成可验证的字节码。对于非`void`目标方法,当执行到`ret`指令时,栈中包含返回类型的值。将`ret`替换为跳转指令后,在编织方法中,栈顶会持有返回类型的值。
#### 5. 编织时替换泛型变量
当应用泛型建议时,Yiihaw会更改存储`Proceed`结果的变量类型。例如,对于泛型建议方法:
```csharp
public static T Advice<T>() {
T result = JoinPointContext.Proceed<T>();
...
return result;
}
```
如果目标方法返回类型为`int`:
```csharp
public int Target() {
...
}
```
Yiihaw会将变量`result`的类型从`T`修改为`int`。对于返回`void`的方法,Yiihaw会移除存储`Proceed`结果的变量以及所有引用它的指令。
#### 6. 更新代码和变量引用
当所有指令都转移到编织方法后,编织器会扫描这些指令,查找悬空的代码地址和未优化的指令。会使用一个映射表更新所有此类引用,并且为了优化,会检查是否可以将指令修改为短格式。
#### 7. 编织时处理GetTarget
`GetTarget`方法可用于获取目标方法的接收者对象。例如:
```csharp
public static T Advice<T>() {
...
TargetClass tgt;
tgt = JoinPointContext.GetTarget<TargetClass>();
tgt.SomeMethod();
return JoinPointContext.Proceed<T>();
}
```
在编织时,Yiihaw会验证类型参数`TargetClass`是否与目标方法的接收者类型兼容。如果兼容,会将对`GetTarget`方法的调用替换为`ldarg.0`指令,加载目标方法的`this`引用。
#### 8. 连接点API
除了`Proceed`和`GetTarget`方法,Yiihaw API还包含几个可以从建议方法调用的属性,如下表所示:
| 属性/方法 | 类型 | 值 |
| --- | --- | --- |
| AccessSpecifier | string | 被拦截方法的访问修饰符 |
| DeclaringType | Type | 被拦截方法的声明类型 |
| DeclaringTypeAsString | string | 被拦截方法声明类型的名称 |
| GetTarget⟨T⟩ | T | 目标方法的接收者:其`this`引用 |
| IsStatic | bool | 如果目标方法是静态的为`true`,否则为`false` |
| Name | string | 目标方法的名称 |
| ParameterNames | string[] | 被拦截方法的参数名称 |
| ParameterTypes
0
0
复制全文


