一. 介绍

最近充能看书,在书上看到函数调用可以 " 通过 ldftn 获得函数指针,然后使用 calli 指令 " 来进行调用,并说这种行为 " 类似 C 的函数指针,但是 C# 不支持这种行为 ",那么这是一种什么样的调用呢?我翻阅了一些资料,才知道 ldftn 和 calli 分别是 IL 语言中的两个指令 ,也就是说这是一种基于 IL 语言的调用。

事实上,C#确实不直接支持这种方式调用函数,但是却可以通过 Emit 类的相关方法来构造 IL 来间接的实现调用。那么,我们开始看看是怎么实现的吧。

二. 实现准备和实现原理

1. 首先我们要实现的方法调用,首先我们有如下类:

  1. public class A
  2. {
  3. public void Test(int num)
  4. {
  5. Console.WriteLine(num);
  6. }
  7.  
  8. public static void Test2(int num)
  9. {
  10. Console.WriteLine(num);
  11. }
  12. }

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)一个简单的实例调用的例子:

  1. .locals init (native int fnptr)
  2. ...
  3. ldfrn void [mscorlib]System.Console::WriteLine(int32)
  4. stloc. //本地变量中存储函数指针
  5. ...
  6. ldc.i4 //加载参数
  7. ldloc.
  8. callo void(int32)
  9. ...

下面我们看一下怎么实现对 Test 和 Test2 的调用的,直接上菜...

三. IL 代码实现

IL 代码如下:

  1. .assembly extern mscorlib
  2. {
  3. auto
  4. }
  5. .assembly MyTest {}
  6. .module MyTest.exe
  7. .class public A
  8. {
  9. .method public specialname void .ctor()
  10. {
  11. ldarg.
  12. call instance void [mscorlib]System.Object::.ctor()
  13. ret
  14. }
  15.  
  16. .method public void Test(int32 param_0)
  17. {
  18. ldarg.
  19. call void [mscorlib]System.Console::WriteLine(int32)
  20. ret
  21. }
  22.  
  23. .method public static void Test2(int32 param_0)
  24. {
  25. ldarg.
  26. call void [mscorlib]System.Console::WriteLine(int32)
  27. ret
  28. }
  29. }
  30. .method public static void Main()
  31. {
  32. .entrypoint
  33. .locals (class A a,int32 v_1)
  34. //实例方法调用
  35. newobj instance void A::.ctor()
  36. stloc.
  37. ldftn instance void A::Test(int32)
  38. stloc.
  39. ldloc.
  40. ldc.i4
  41. ldloc.
  42. calli instance void(int32)
  43.  
  44. //静态方法调用
  45. ldftn void A::Test2(int32)
  46. stloc.
  47. ldc.i4
  48. ldloc.
  49. calli void(int32)
  50. ret
  51. }

四. C# 用 Emit 类来实现

C# 代码:

  1. public class CreateTypeHelper
  2. {
  3. public static Type CreateMethodCallingType()
  4. {
  5. //获得当前的程序域
  6. AppDomain currentDomain = Thread.GetDomain();
  7. //创建这个类的程序集
  8. AssemblyName assemblyName = new AssemblyName();
  9. assemblyName.Name = "DynamicAssembly";
  10. AssemblyBuilder assemblyBuilder = currentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
  11. //创建模块
  12. ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MethodCallingModule", "MethodCalling.dll");
  13. //创建类型
  14. TypeBuilder typeBuilder = moduleBuilder.DefineType("MethodCalling", TypeAttributes.Public);
  15. //创建一个方法
  16. MethodBuilder methodBuilder = typeBuilder.DefineMethod("CalliMethodCall", MethodAttributes.Public | MethodAttributes.Static, typeof(void), new Type[]);
  17. //IL
  18. ILGenerator il = methodBuilder.GetILGenerator();
  19. //.locals (class A a,int32 v_1)
  20. il.DeclareLocal(typeof(A));
  21. il.DeclareLocal(typeof(Int32));
  22.  
  23. //newobj instance void A::.ctor()
  24. ConstructorInfo constructorInfo = typeof(A).GetConstructor(new Type[]);
  25. il.Emit(OpCodes.Newobj, constructorInfo);
  26. //stloc.0
  27. il.Emit(OpCodes.Stloc_0);
  28. //获得A.Test方法
  29. MethodInfo methodInfo = typeof(A).GetMethod("Test", BindingFlags.Public | BindingFlags.Instance);
  30. //ldftn instance void A::Test(int32)
  31. il.Emit(OpCodes.Ldftn, methodInfo);
  32. //stloc.1
  33. il.Emit(OpCodes.Stloc_1);
  34. //ldloc.0
  35. il.Emit(OpCodes.Ldloc_0);
  36. //ldc.i4 120
  37. il.Emit(OpCodes.Ldc_I4, );
  38. ////ldloc.1
  39. il.Emit(OpCodes.Ldloc_1);
  40. //calli instance void(int32)
  41. il.EmitCalli(OpCodes.Calli, CallingConventions.HasThis, typeof(void), new Type[] { typeof(Int32) }, null);
  42. ////获得A.Test方法
  43. MethodInfo methodInfo1 = typeof(A).GetMethod("Test2", BindingFlags.Public | BindingFlags.Static);
  44. //ldftn void A::Test2(int32)
  45. il.Emit(OpCodes.Ldftn, methodInfo1);
  46. //stloc.1
  47. il.Emit(OpCodes.Stloc_1);
  48. //ldc.i4 233
  49. il.Emit(OpCodes.Ldc_I4, );
  50. //ldloc.1
  51. il.Emit(OpCodes.Ldloc_1);
  52. //calli void(int32)
  53. il.EmitCalli(OpCodes.Calli,CallingConventions.Standard, typeof(void), new Type[] { typeof(Int32) }, null);
  54. //ret
  55. il.Emit(OpCodes.Ret);
  56.  
  57. Type retType = typeBuilder.CreateType();
  58. assemblyBuilder.Save("MethodCalling.dll");
  59.  
  60. return retType;
  61. }
  62. }

上端调用方法:

  1. Type type = CreateTypeHelper.CreateMethodCallingType();
  2. //获得方法
  3. MethodInfo methodInfo = type.GetMethod("CalliMethodCall");
  4. if (methodInfo != null)
  5. {
  6. methodInfo.Invoke(null, null);
  7. }

执行结果:

五. 验证结果

最后我们验证一下动态生成的类。在代码中,我们创建了一个 " 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. Java实现网络聊天中使用的socket API与Linux socket API之间的关系

    尝试着用Java编写一个网络聊天程序,发现总不如网上写的好,所以就直接引用了网上大神的优秀代码.代码如下: package project1; import java.awt.*; import ja ...

  2. VSCode 使用 ESLint + Prettier 来统一 JS 代码

    环境: VSCode 1.33.1 Node.js 8.9.1 一.ESLint 1.介绍 ESLint是最流行的JavaScript Linter. Linter 是检查代码风格/错误的小工具.其他 ...

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

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

  4. 记一次Tomcat启动报错Failed to start component [StandardEngine[Catalina].Standard

    今天启动项目的时候,发现tomcat一直报错,之前都一直没有问题的啊,提示       org.apache.catalina.LifecycleException: Failed to start ...

  5. Java描述设计模式(09):装饰模式

    本文源码:GitHub·点这里 || GitEE·点这里 一.生活场景 1.场景描述 孙悟空有七十二般变化,他的每一种变化都给他带来一种附加的本领.他变成鱼儿时,就可以到水里游泳:他变成鸟儿时,就可以 ...

  6. idea中配置maven的骨架本地下载方式

    由于我们使用maven的骨架创建的时候,maven需要联网进行骨架的下载,如果断网了,则骨架不能正常下载,为了防止这种情况,我们可以配置本地下载,当已经联网下载过一次后,以后每次进行下载都会从本地下载 ...

  7. kafka cmd首个单机例子配置

    下载地址:http://kafka.apache.org/downloads       http://mirror.bit.edu.cn/apache/kafka/2.3.0/kafka_2.12- ...

  8. JQuery 获取元素到浏览器可视窗口边缘的距离

    获取元素到浏览器可视窗口边缘的距离 by:授客 QQ:1033553122 1.   测试环境 JQuery-3.2.1.min.js 下载地址: https://gitee.com/ishouke/ ...

  9. 转战物联网·基础篇07-深入理解MQTT协议之控制报文(数据包)格式

      在MQTT协议中,一个控制报文(数据包)的结构按照前后顺序分如下三部分: 结构名 中文名 解释说明 Fixed header 固定报头 报文的最开始部分,所有报文都包含这个部分 Variable ...

  10. bay——RAC 关闭和启动顺序,状态查看.txt

    oracle 11g rac 关闭和启动顺序,状态查看https://www.cnblogs.com/hellojesson/p/4501112.html----------------------- ...