一. 介绍
最近充能看书, 在书上看到函数调用可以 "通过 ldftn 获得函数指针, 然后使用 calli 指令" 来进行调用, 并说这种行为 "类似 C 的函数指针, 但是 C# 不支持这种行为", 那么这是一种什么样的调用呢? 我翻阅了一些资料, 才知道 ldftn 和 calli 分别是 IL 语言中的两个指令 , 也就是说这是一种基于 IL 语言的调用.
事实上, C# 确实不直接支持这种方式调用函数, 但是却可以通过 Emit 类的相关方法来构造 IL 来间接的实现调用. 那么, 我们开始看看是怎么实现的吧.
二. 实现准备和实现原理
1. 首先我们要实现的方法调用, 首先我们有如下类:
- public class A
- {
- public void Test(int num)
- {
- Console.WriteLine(num);
- }
- public static void Test2(int num)
- {
- Console.WriteLine(num);
- }
- }
2. IL 语言相关:
(1)Ldftn 指令:
- 语法: ldftn <token>
- 功能: 把函数指针加载到由 MethodDef 或 MemberRef 类型的 <token> 所指定的方法上
(2)Calli 指令:
- 语法: calli <token>
- 功能: 先从栈上弹出函数指针, 再从栈上弹出所有的参数, 然后根据 <toke> 指定的方法签名进行间接方法调用.<token> 必须是有效的 StandAloneSig 标记. 函数指针必须位于栈顶. 如过方法返回数值, 那么该数值在调用完成后被压入栈上.
★ 3. IL 调用函数方法的一般流程:
一般在 IL 中使用方式分为两大类, 一种是调用托管函数, 另外一种是调用非托管的函数, 这里我们暂不考虑对非托管函数的调用. 对于托管函数, 按照其调用方法是不是要引用一个对象实列, 可以分为静态方法和实例方法.
接下来就对这两种类型的方法, 分别描述一下 IL 代码的一般流程.
(1) 实例方法:
过程如下:
- 通过 "newobj instance 构造函数签名" 先创建一个实例对象, 并将对象从栈顶弹出保存到局部变量
- 通过 "ldftn instance 实例方法签名" 来获得函数方法的指针, 调用完成后这个指针会被放置于栈顶, 同理也将栈顶的函数指针弹出并保存到局部变量
- 把实例对象放置到栈顶, 由于实例方法要 this 指向的对象, 所以这个对象会作为第一个参数 arg.0 作为 this
- 按照函数的调用的参数的顺序, 依次将变量放置到栈顶
- 把函数的指针放置到栈顶
- 通过 "calli instance 实例方法签名" 来进行函数的调用, 调用完成之后会把返回值放置与栈顶, 最后把栈顶的返回值弹出并保存使用
(2) 静态方法:
静态方法不需要 this 指向的对象, 所以这边也不需要先创建一个对象, 过程如下:
- 通过 "ldftn 静态方法签名" 来获得函数方法的指针, 调用完成后这个指针会被放置于栈顶, 将栈顶的函数指针弹出并保存到局部变量
- 按照函数的调用的参数的顺序, 依次将变量放置到栈顶
- 把函数的指针放置到栈顶
- 通过 "calli 静态方法签名" 来进行函数的调用, 调用完成之后会把返回值放置与栈顶, 最后把栈顶的返回值弹出并保存使用
(3) 一个简单的实例调用的例子:
- .locals init (native int fnptr)
- ...
- ldfrn void [mscorlib]System.Console::WriteLine(int32)
- stloc.0 // 本地变量中存储函数指针
- ...
- ldc.i4 12345 // 加载参数
- ldloc.0
- callo void(int32)
- ...
下面我们看一下怎么实现对 Test 和 Test2 的调用的, 直接上菜...
三. IL 代码实现
IL 代码如下:
- .assembly extern mscorlib
- {
- auto
- }
- .assembly MyTest {}
- .module MyTest.exe
- .class public A
- {
- .method public specialname void .ctor()
- {
- ldarg.0
- call instance void [mscorlib]System.Object::.ctor()
- ret
- }
- .method public void Test(int32 param_0)
- {
- ldarg.1
- call void [mscorlib]System.Console::WriteLine(int32)
- ret
- }
- .method public static void Test2(int32 param_0)
- {
- ldarg.0
- call void [mscorlib]System.Console::WriteLine(int32)
- ret
- }
- }
- .method public static void Main()
- {
- .entrypoint
- .locals (class A a,int32 v_1)
- // 实例方法调用
- newobj instance void A::.ctor()
- stloc.0
- ldftn instance void A::Test(int32)
- stloc.1
- ldloc.0
- ldc.i4 120
- ldloc.1
- calli instance void(int32)
- // 静态方法调用
- ldftn void A::Test2(int32)
- stloc.1
- ldc.i4 233
- ldloc.1
- calli void(int32)
- ret
- }
四. C# 用 Emit 类来实现
- C# 代码:
- public class CreateTypeHelper
- {
- public static Type CreateMethodCallingType()
- {
- // 获得当前的程序域
- AppDomain currentDomain = Thread.GetDomain();
- // 创建这个类的程序集
- AssemblyName assemblyName = new AssemblyName();
- assemblyName.Name = "DynamicAssembly";
- AssemblyBuilder assemblyBuilder = currentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
- // 创建模块
- ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MethodCallingModule", "MethodCalling.dll");
- // 创建类型
- TypeBuilder typeBuilder = moduleBuilder.DefineType("MethodCalling", TypeAttributes.Public);
- // 创建一个方法
- MethodBuilder methodBuilder = typeBuilder.DefineMethod("CalliMethodCall", MethodAttributes.Public | MethodAttributes.Static, typeof(void), new Type[0]);
- //IL
- ILGenerator il = methodBuilder.GetILGenerator();
- //.locals (class A a,int32 v_1)
- il.DeclareLocal(typeof(A));
- il.DeclareLocal(typeof(Int32));
- //newobj instance void A::.ctor()
- ConstructorInfo constructorInfo = typeof(A).GetConstructor(new Type[0]);
- il.Emit(OpCodes.Newobj, constructorInfo);
- //stloc.0
- il.Emit(OpCodes.Stloc_0);
- // 获得 A.Test 方法
- MethodInfo methodInfo = typeof(A).GetMethod("Test", BindingFlags.Public | BindingFlags.Instance);
- //ldftn instance void A::Test(int32)
- il.Emit(OpCodes.Ldftn, methodInfo);
- //stloc.1
- il.Emit(OpCodes.Stloc_1);
- //ldloc.0
- il.Emit(OpCodes.Ldloc_0);
- //ldc.i4 120
- il.Emit(OpCodes.Ldc_I4, 120);
- ////ldloc.1
- il.Emit(OpCodes.Ldloc_1);
- //calli instance void(int32)
- il.EmitCalli(OpCodes.Calli, CallingConventions.HasThis, typeof(void), new Type[] { typeof(Int32) }, null);
- //// 获得 A.Test 方法
- MethodInfo methodInfo1 = typeof(A).GetMethod("Test2", BindingFlags.Public | BindingFlags.Static);
- //ldftn void A::Test2(int32)
- il.Emit(OpCodes.Ldftn, methodInfo1);
- //stloc.1
- il.Emit(OpCodes.Stloc_1);
- //ldc.i4 233
- il.Emit(OpCodes.Ldc_I4, 233);
- //ldloc.1
- il.Emit(OpCodes.Ldloc_1);
- //calli void(int32)
- il.EmitCalli(OpCodes.Calli,CallingConventions.Standard, typeof(void), new Type[] { typeof(Int32) }, null);
- //ret
- il.Emit(OpCodes.Ret);
- Type retType = typeBuilder.CreateType();
- assemblyBuilder.Save("MethodCalling.dll");
- return retType;
- }
- }
上端调用方法:
- Type type = CreateTypeHelper.CreateMethodCallingType();
- // 获得方法
- MethodInfo methodInfo = type.GetMethod("CalliMethodCall");
- if (methodInfo != null)
- {
- methodInfo.Invoke(null, null);
- }
执行结果:
五. 验证结果
最后我们验证一下动态生成的类. 在代码中, 我们创建了一个 "MethodCalling.dll" 用来保存动态生成的类, 里面承载了我们的 Emit 代码生成的 IL 代码, 如下图:
用 ILDasm.exe 查看后如下图:
看了一下与我们写的 IL 代码基本一致, 今天对 Calli 调用函数方法的研究基本成功!
来源: http://www.bubuko.com/infodetail-3300800.html