C#如何用IL和Emit类通过Calli来实现实例函数与静态函数的调用
一. 介绍
最近充能看书,在书上看到函数调用可以 " 通过 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. //本地变量中存储函数指针
...
ldc.i4 //加载参数
ldloc.
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.
call instance void [mscorlib]System.Object::.ctor()
ret
} .method public void Test(int32 param_0)
{
ldarg.
call void [mscorlib]System.Console::WriteLine(int32)
ret
} .method public static void Test2(int32 param_0)
{
ldarg.
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.
ldftn instance void A::Test(int32)
stloc.
ldloc.
ldc.i4
ldloc.
calli instance void(int32) //静态方法调用
ldftn void A::Test2(int32)
stloc.
ldc.i4
ldloc.
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[]);
//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[]);
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, );
////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, );
//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 调用函数方法的研究基本成功!
C#如何用IL和Emit类通过Calli来实现实例函数与静态函数的调用的更多相关文章
- 如何用ActiveQt写导出类
如何用ActiveQt写导出类 最近一直在用ActiveQt框架来写ActiveX插件, 由于项目需要提示类的导出, 所以上午捣鼓了一下, 现在记录记录.其实内容主要是把Qt手册里自己用到的部分整理一 ...
- 【转载】 C++多继承中重写不同基类中相同原型的虚函数
本篇随笔为转载,原文地址:C++多继承中重写不同基类中相同原型的虚函数. 在C++多继承体系当中,在派生类中可以重写不同基类中的虚函数.下面就是一个例子: class CBaseA { public: ...
- 类中用const限定的成员函数
本文转自http://blog.csdn.net/whyglinux/article/details/602329 类的成员函数后面加 const,表明这个函数不会对这个类对象的数据成员(准确地说是非 ...
- 使用iskindofclass来发现对象是否是某类或其子类的实例
发现对象是否是特定类或其子类的实例 要发现对象是否是某类或其子类的实例,请在对象上调用 isKindOfClass: 方法.当应用程序需要发现其响应的消息(实现的或继承的),它有时进行以上的检查. s ...
- C++@类的静态成员变量和静态成员函数
参考: http://blog.csdn.net/morewindows/article/details/6721430 http://www.cnblogs.com/lzjsky/archive/2 ...
- 基类中定义的虚函数在派生类中重新定义时,其函数原型,包括返回类型、函数名、参数个数、参数类型及参数的先后顺序,都必须与基类中的原型完全相同 but------> 可以返回派生类对象的引用或指针
您查询的关键词是:c++primer习题15.25 以下是该网页在北京时间 2016年07月15日 02:57:08 的快照: 如果打开速度慢,可以尝试快速版:如果想更新或删除快照,可以投诉快照. ...
- 用C++设计一个不能被继承的类(用私有构造函数+友元函数)
题目:用C++设计一个不能被继承的类. 分析:这是Adobe公司2007年校园招聘的最新笔试题.这道题除了考察应聘者的C++基本功底外,还能考察反应能力,是一道很好的题目. 在Java中定义了关键字f ...
- Python笔记(七):字典、类、属性、对象实例、继承
(一) 简单说明 字典是Python的内置数据结构,将数据与键关联(例如:姓名:张三,姓名是键,张三就是数据).例如:下面这个就是一个字典 {'姓名': '张三', '出生日期': '2899-08 ...
- python基础——类名称空间与对象(实例)名称空间
python基础--类名称空间与对象(实例)名称空间 1 类名称空间 创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性 而类的良好总属性:数据属性和函数属性 其中类 ...
随机推荐
- 【游戏开发】网络编程之浅谈TCP粘包、拆包问题及其解决方案
引子 现如今手游开发中网络编程是必不可少的重要一环,如果使用的是TCP协议的话,那么不可避免的就会遇见TCP粘包和拆包的问题,马三觉得haifeiWu博主的 TCP 粘包问题浅析及其解决方案 这篇博客 ...
- 拥抱webpack4,有效缩减构建时间57%+
背景 最近有感觉到,随着系统模块数量的增加,wepack编译打包的速度越来越慢,于是我想给项目做一下优化升级,也借此机会系统地学习一下webpack4. 升级过程 当前版本 "depende ...
- Selenium(三):操控元素的基本方法
1. 操控元素的基本方法 选择到元素之后,我们的代码会返回元素对应的 WebElement对象,通过这个对象,我们就可以操控元素了. 操控元素通常包括: 点击元素 在元素中输入字符串,通常是对输入框这 ...
- JMeter内存溢出:java.lang.OutOfMemoryError: Java heap space解决方法
一.问题原因 用JMeter压测,有时候当模拟并发请求较大或者脚本运行时间较长时,JMeter会停止,报OOM(内存溢出)错误. 原因是JMeter是一个纯Java开发的工具,内存由java虚拟机JV ...
- [browser srceen]、很多未知望大神告知、简单写了个拖拽
未知作用的有.如果也有像我1样好奇的小伙伴了解了麻烦告知 // console.log(window.screen.availWidth);//未知效果 // console.log(window.s ...
- ArcGIS Desktop中为图像设置成不显示背景色
加载的栅格图层在arcgis中显示有黑色背景 方法一:如果有矢量边界的话,直接用矢量边界把黑色背景裁剪掉即可. 方法二: 如果是RGB三色: 这个只是不显示,并不改变原有的值 如果是值: 附:为影像数 ...
- PHP 部分语法(二)
array() 创建数组: 1.数值数组:带数字 ID 键的数组 2.关联数组:带有指定键的数组,键关联一个值 3.多维数组:包含一个或多个数组的数组 $arr = array("Hello ...
- 微信小程序中换行,空格(多个空格)写法
在小程序中HTML的网页实体无法正常使用,小程序中的写法为: 一.空格,换行 <text>你好!\t七月流火啊!\n我在下一行</text> \t 空格( 多个只会显示一个空格 ...
- 教你两招用纯CSS写Tab切换
说到Tab切换,你可能首先想到的就是使用jQuery,短短几行代码就可以轻松搞定一个Tab切换. 而今天所要分享的,是使用 0 行JS代码来实现Tab切换! 具体效果如下: Tab切换 方法一:模 ...
- Git操作删除 untracked files
最近使用git 管理项目的时候,编译过程中出现了很多中间文件,今天发现使用 git clean 命令可以很方便进行清除: # 删除 untracked files git clean -f # 连 u ...