一. 介绍

最近充能看书,在书上看到函数调用可以 " 通过 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来实现实例函数与静态函数的调用的更多相关文章

  1. 如何用ActiveQt写导出类

    如何用ActiveQt写导出类 最近一直在用ActiveQt框架来写ActiveX插件, 由于项目需要提示类的导出, 所以上午捣鼓了一下, 现在记录记录.其实内容主要是把Qt手册里自己用到的部分整理一 ...

  2. 【转载】 C++多继承中重写不同基类中相同原型的虚函数

    本篇随笔为转载,原文地址:C++多继承中重写不同基类中相同原型的虚函数. 在C++多继承体系当中,在派生类中可以重写不同基类中的虚函数.下面就是一个例子: class CBaseA { public: ...

  3. 类中用const限定的成员函数

    本文转自http://blog.csdn.net/whyglinux/article/details/602329 类的成员函数后面加 const,表明这个函数不会对这个类对象的数据成员(准确地说是非 ...

  4. 使用iskindofclass来发现对象是否是某类或其子类的实例

    发现对象是否是特定类或其子类的实例 要发现对象是否是某类或其子类的实例,请在对象上调用 isKindOfClass: 方法.当应用程序需要发现其响应的消息(实现的或继承的),它有时进行以上的检查. s ...

  5. C++@类的静态成员变量和静态成员函数

    参考: http://blog.csdn.net/morewindows/article/details/6721430 http://www.cnblogs.com/lzjsky/archive/2 ...

  6. 基类中定义的虚函数在派生类中重新定义时,其函数原型,包括返回类型、函数名、参数个数、参数类型及参数的先后顺序,都必须与基类中的原型完全相同 but------> 可以返回派生类对象的引用或指针

      您查询的关键词是:c++primer习题15.25 以下是该网页在北京时间 2016年07月15日 02:57:08 的快照: 如果打开速度慢,可以尝试快速版:如果想更新或删除快照,可以投诉快照. ...

  7. 用C++设计一个不能被继承的类(用私有构造函数+友元函数)

    题目:用C++设计一个不能被继承的类. 分析:这是Adobe公司2007年校园招聘的最新笔试题.这道题除了考察应聘者的C++基本功底外,还能考察反应能力,是一道很好的题目. 在Java中定义了关键字f ...

  8. Python笔记(七):字典、类、属性、对象实例、继承

    (一)  简单说明 字典是Python的内置数据结构,将数据与键关联(例如:姓名:张三,姓名是键,张三就是数据).例如:下面这个就是一个字典 {'姓名': '张三', '出生日期': '2899-08 ...

  9. python基础——类名称空间与对象(实例)名称空间

    python基础--类名称空间与对象(实例)名称空间 1 类名称空间 创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性 而类的良好总属性:数据属性和函数属性 其中类 ...

随机推荐

  1. 【RTOS】基于V7开发板的RTX5和FreeRTOS带CMSIS-RTOS V2封装层的模板例程下载,AC6和AC5两个版本

    说明: 1.使用MDK的RTE环境开发RTX5和FreeRTOS,简单易移植,统一采用CMSIS-RTOS V2封装层. 2.DTCM是H7里面性能最高的RAM,主频400MHz,跟内核速度一样,所以 ...

  2. error: (-210:Unsupported format or combination of formats) [Start]FindContours supports only CV_8UC1 images when mode != CV_RETR_FLOODFILL otherwise supports CV_32SC1 images only in

    问题: error: (-210:Unsupported format or combination of formats) [Start]FindContours supports only CV_ ...

  3. Delphi xe 10.3.2-快递接口封装-【快递鸟(即时查询和单号识别)】

    编译环境:Windows 7 +Delphi xe 10.3.2 封装了快递鸟接口,注意的坑:MD5要转为小写. function TKDniaoAPI.StrtoMd5(const str: str ...

  4. ASP.NET Core Web 应用程序系列(三)- 在ASP.NET Core中使用Autofac替换自带DI进行构造函数和属性的批量依赖注入(MVC当中应用)

    在上一章中主要和大家分享了在ASP.NET Core中如何使用Autofac替换自带DI进行构造函数的批量依赖注入,本章将和大家继续分享如何使之能够同时支持属性的批量依赖注入. 约定: 1.仓储层接口 ...

  5. 详解Condition的await和signal等待/通知机制

    本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...

  6. 爬虫爬取m3u8视频文件

    一.m3u8视频格式 一般m3u8文件和 视频流ts文件放在同一目录 而m3u8文件格式存放的一般都是ts 文件的一个列表 二.根据m3u8视频存放以及写法的规律 思路 我们一般网站上能找到的m3u8 ...

  7. 史诗级最强教科书式“NIO与Netty编程”

    史诗级最强教科书式“NIO与Netty编程” 1.1 概述 1.2 文件IO 1.2.1 概述和核心API 1.2.2 案例 1.3 网络IO 1.3.1 概述和核心API 3.4 AIO编程 3.5 ...

  8. Cesium专栏-空间分析之坡向分析(附源码下载)

    Cesium Cesium 是一款面向三维地球和地图的,世界级的JavaScript开源产品.它提供了基于JavaScript语言的开发包,方便用户快速搭建一款零插件的虚拟地球Web应用,并在性能,精 ...

  9. 【Gradle】Java Gradle 插件

    Java Gradle 插件 如何应用 apply plugin:'java' Java插件约定的项目结构 Project |--build.gradle |--src |--main |--java ...

  10. angularjs $http请求网络数据并展示

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...