Emit应用中的常用技巧

Emit是.net Framework的Reflection的重要组成部分,其地位类同于Java中的cglib,在一些项目中特别是一些公共项目中不可或缺。在我的Kanas.net框架的数据囊中便使用了Emit,并且不可替代。
Emit的典型应用如下:
1.动态代理,参见: http://www.castleproject.org/index.php/DynamicProxy
2.解除对非托管dll的绑定,参见: http://www.codeproject.com/dotnet/DynamicDllImport.asp
3.工具及IDE插件的开发
4.公共代码安全模块的开发
本文与大家分享一些常用的技巧。
 
1.布局
定义动态类的一种比较好的方式是定义一个基类,留下一些虚拟或抽象方法由动态类继承。比较合理的方案是在这个基类中定义一个静态方法,返回动态类型的Type实例甚至直接返回该基类的实例(或者称代理)。在Emit布局中需要考虑的问题是Assembly的名称和类型名称,不可以重复,如果有可能由Emit生成多个动态类型,并且不是一次生成,这个问题就会突出出来。我的意见是Assembly的名称采用固定名称头加上随机字符串名称尾的方式。类型名称则可以采用手工方式定义来避免重复。
 
2.重载方法(override)
在所有Method Declare的方法中,重载一个方法是最简单的了。你可以简单地使用反射就可以拿到所有的Parameter定义所需要的Type,而不需要挖空心思去寻找类型的反射实例。
重载方法有两种情况,一种是重载基类的虚拟或者抽象方法,另一种是实现接口方法(严格说并不是重载)。这两种情况公共的地方是MethodAttributes中必须包括MethodAttributes.HideBySig和MethodAttributes.Virtual。前一种情况必须还包括MethodAttributes.ReuseSlot,而后一种情况必须还包括MethodAttributes.NewSlot。获取参数原型的方法非常简单,通过反射遍历所有的参数,取出类型来存入一个数组即可。
片段一:重载一个方法
 
private ILGenerator OverrideMethod(TypeBuilder typeBuilder, MethodInfo baseMethod)
{
ParameterInfo[] paramInfos = baseMethod.GetParameters();
Type[] argTypes = new Type[paramInfos.Length];
int i = 0;
foreach (ParameterInfo pi in paramInfos)
{
argTypes[i ++] = pi.ParameterType;
}
MethodAttributes attributes = MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.ReuseSlot//MethodAttributes.NewSlot;
if (baseMethod.IsPublic)
{
Attributes |= MethodAttributes.Public;
}
MethodBuilder method = typeBuilder.DefineMethod(baseMethod.Name, attributes, baseMethod.ReturnType, argTypes);
return method.GetILGenerator();
}
 
片段二:重载一个接口方法实现
 
private ILGenerator OverrideMethod(TypeBuilder typeBuilder, MethodInfo baseMethod)
{
ParameterInfo[] paramInfos = baseMethod.GetParameters();
Type[] argTypes = new Type[paramInfos.Length];
int i = 0;
foreach (ParameterInfo pi in paramInfos)
{
argTypes[i ++] = pi.ParameterType;
}
MethodAttributes attributes = MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.ReuseSlot//MethodAttributes.NewSlot | MethodAttributes.Public;
MethodBuilder method = typeBuilder.DefineMethod(baseMethod.Name, attributes, baseMethod.ReturnType, argTypes);
typeBuilder.DefineMethodOverride(method, baseMethod);
return method.GetILGenerator();
}
 
3.加入到AppDomain(应用域)
如果Emit返回动态类型后,立即建立实例并输出参与后期的行为,不涉及AppDomain问题。但如果输出的只是动态类型,就必须将动态类型所在的Assembly加入到AppDomain中。类型是元数据,当然是Singleton的,所以采用一个静态的Hashtable来管理就够了。每生成一个动态类型就将该类型所在的Assembly以类型为关键字存入Hashtable,然后给定AppDomain的CurrentDomain实例的AssemblyResolve事件,在事件Handler中查找指定类型对应的Assembly作为返回。
需要特别说明的是,给CurrentDomain设定事件只需要在每次定义Assembly时设定,而不是定义每个动态类型时设定。
 
4.获取参数类型
在C#.net中获取类型的Type实例并那么自由。例如void*这样的参数类型就必须用到unsafe限定。而带有ref或者out限定符以及带有“伪标签”的参数就更难获取参数类型的Type实例了。例如[In, Out] string。
这样的情况下,比较简单的方式就是定义一个“伪方法”,将所有比较变态一点的参数都用到,目的只是为了反射而反射。例如可以定义这个方法:
 
private static void PseudCall(ref IntPtr p1, [In, Out] string[] p2, [MarshalAs(UnmanagedType.LPArray)] byte[] p3, ref SE_Error p4)
{
}
 
然后在运行时通过以下方式获取这个类型集合:
 
MethodInfo tmi = typeof(EngineFactory).GetMethod("PseudCall", BindingFlags.NonPublic | BindingFlags.Static);
ParameterInfo[] pis = tmi.GetParameters();
Type[] types = new Type[pis.Length];
for (int i = 0; i < pis.Length; i ++)
{
types[i] = pis[i].ParameterType;
}
 
 
5.循环分支
大部分情况下,Emit生成的动态类型及方法都不能太复杂,所以采用“短分支”指令是很自然的事情。但是凡事总有例外,特别是有些代码是通过循环来实现的,在这种情况下,就有可能通过“短分支”想实现长距离的代码跳转。
例如在C#中:
 
{
case 1:

break;
case 2:

break;

case 100:

break;
}
 
以上代码用MSIL实现时,case 1的break有可能无法通过短跳转跳到分支以外,但是按以下方式实现却是非常保险的:

{
case 1:

goto b1;
case 2:

b1:
goto b2;

case 100:

b99:
goto b100;
}
b100:
这样只要保证每个case中生成的代码长度不超过128字节,就有绝对的安全保证。
实现这样的逻辑其实非常简单:

Label ln = methodGenerator.DefineLabel();
bool isFirst = true;
foreach (int i in list)
{
// 生成case中的代码
……
// 此处需要跳转
if (isFirst)
{
isFirst = false;
}
else
{
methodGenerator.MarkLabel(ln);
ln = methodGenerator.DefineLabel();
}
methodGenerator.Emit(OpCodes.Br_S, ln);
// 不跳转的后续处理

}
// 退出前的处理

methodGenerator.MarkLabel(ln);
 
 

你可能感兴趣的:(职场,休闲,Emit应用中的常用技巧)