前文回顾:

《用CIL写程序:你好,沃尔德》

《用CIL写程序:写个函数做加法》

前言:

今天是乙未羊年的第一天,小匹夫先在这里给各位看官拜个年了。不知道各位看官是否和匹夫一样,摸键盘的手都已经有点生疏了呢?所以,为了不忘却程序猿的使命,不冷落程序猿最好的伙伴--键盘。匹夫决定来写《用CIL写程序》的最新一篇文章。可是写什么主题呢?之前匹夫也介绍过CIL其实也是面向对象的,所以寻思着大过年的,不如就写一个类,一个用来抽象化小匹夫的类吧,既可以介绍下小匹夫,小匹夫也可以借这个类给各位拜年。那么顺序由上到下,无外乎如何声明一个类类成员如何定义,以至于到后来如何实例化一个类,并且调用实例的各个方法,当然本文的完整CIL代码各位可以在附录部分看到。

披着慕容小匹夫的外衣

OK,那么声明一个类的第一步是什么呢?定义一个类的名字?写构造函数?NO,NO。

声明一个类的第一步,自然是要告诉别人你声明的是一个类!不是方法,不是变量,而是一个类。那么我们仿照前两篇文章中介绍的声明方法的方式:.method,来声明我们的类。那么各位是不是已经想到了呢?对,我们直接使用“.class”指令来声明一个类。

那么第二步呢?对,既然知道它是一个类了,那么我们显然还可以指定关键字比如abstract、sealedpublicprivate或者interface等等,而在CIL中很多都是可以省略的,所以这里我们也就省略了。之后我们还需要给它起个名字来标识它,既然题目已经叫做《这个叫慕容小匹夫的类》了,那么我们的类的名字就叫做MurongXiaoPiFu。

之后第三步呢?对啊,貌似应该有个构造函数啊。但有时候我们写C#代码的时候,并不需要自己手动写构造函数呀。可为什么到了CIL里就需要自己手动写呢?因为C#的编译器会自动为我们生成构造函数,而CIL的编译器则不会。所以构造函数这个环节我们一定不要忘记。

所以类的声明部分我们就写出来了:

//MurongXiaoPiFu类的声明
.class MurongXiaoPiFu
{
.method public void .ctor()
{
//TODO
}
}

OK,需要的三个步骤走完了。这里总结一下:

  1. .class指令标识我们声明的是一个类
  2. 这个类的关键字和名字紧跟.class之后。这里小匹夫写的类是MurongXiaoPiFu。
  3. 必须要写构造函数”.method public void .ctor()“

好了,要写构造函数这一点我们已经明确了。我们声明完.ctor()之后,该如何实现它呢?那么我们首先考虑一下一个没有参数的构造函数都可能要做一些什么。

匹夫在上一篇文章中详细的介绍过CIL是如何使用堆栈的,包括值类型是如何使用堆栈以及引用类型是如何使用堆栈。此处的MurongXiaoPiFu类其实就是一个引用类型,所以呢?不错,它需要把自己的引用压栈。

那么还有呢?所有的引用类型都派生自System.Object,所以生成一个新的类实例其实是通过System.Object的构造函数来实现的。那么基于以上2点,我们的构造函数的实现也就十分清楚了:

//构造函数.ctor的实现
.method public void .ctor()
{
.maxstack
ldarg. //1.将实例的引用压栈
call instance void [mscorlib]System.Object::.ctor() //2.调用基类的构造函数
ret
}

但是小匹夫觉得写到这里还有点不完整,还缺点啥呢?对啊,面向对象的三大法宝:封装、继承、多态嘛。其中继承和多态都和所谓的继承有很大的关系。所以说到类,不说继承似乎有点不专业。为了不给各位留下匹夫不专业的印象,继承还是不得不说。但是看匹夫你定义的这个类MurongXiaoPiFu貌似没有用上继承啊?

此言差矣。殊不知,MurongXiaoPiFu这个类默认是继承自System.Object这个基类的。只不过当我们省略掉继承的语句时,CIL的编译器会自动将我们的类识别为从System.Object派生而来的,换言之,会被当做是引用类型。那么如果我们不省略继承语句,我们的类该如何显式地实现继承呢?没错,用extend关键字。那么加上继承自System.Object的语句(虽然这是多余的,但是为了演示继承的用法这里还是很low的这样写了)之后,我们完整的声明代码如下:

//MurongXiaoPiFu类的声明
.class MurongXiaoPiFu
extends [mscorlib]System.Object //同样System.Object来自mscorlib程序集
{
//构造函数.ctor的实现
.method public void .ctor()
{
.maxstack
ldarg. //1.将实例的引用压栈
call instance void [mscorlib]System.Object::.ctor() //2.调用基类的构造函数
ret
}
}

好了,行文至此,各位对CIL中如何声明一个类应该就有了一个直观的印象了吧。不过这还不够啊,没有类中的各个成员,这个类也就没有什么实际的用途呀。所以接下来匹夫继续完善这个叫慕容小匹夫的类,来介绍一下小匹夫自己,同时也为各位拜个年。

我的成员我做主

说到类的成员无非就是类的成员变量和类的成员函数

成员变量

那么具体到我们的类,它的成员变量无非就是介绍一个人所需要的信息。无非就是姓名(name),年龄(age),性别(sex),职业(job)这些咯。匹夫相信在C#中,各位都知道如何声明一个成员变量。但是在CIL的世界中呢?应该如何声明一个变量呢?

和声明方法的.method指令类似,这里匹夫再引入一个指令.field用来声明变量。同样.field之后也可以加一些修饰符。

但是写代码对这些变量赋值之前,匹夫还是要强调一下CIL的执行都是依托于堆栈的,也就是要把堆栈记心中。这里我们需要用到stfld指令,这个指令是什么意思呢?简短截说就是把栈中的数据弹出,并赋值给这个类实例中对应的字段。那么之前,显然我们要将那个值先入栈,这样才能出栈赋值给实例中的字段嘛。

所以咯,匹夫的基本信息就是这样的了:


//声明各个变量
.field  public  string name
.field  public  int32  age
.field  public  string sex
.field  public  string job
//为各个成员变量赋值
ldstr "陈嘉栋"
stfld string MurongXiaoPiFu::name
ldc.i4.s 0x19   //25岁
stfld int32 MurongXiaoPiFu::age
ldstr "男人"
stfld string MurongXiaoPiFu::sex
ldstr "程序猿"
stfld string MurongXiaoPiFu::job

成员函数

匹夫的基本资料有了,但是匹夫还想向大伙拜年和介绍自己呢啊。所以光有成员变量还是不够的,我们还需要几个成员函数。说到一个类的函数,首先要关注点什么呢?对啊,一个类中的函数究竟是静态函数呢还是实例函数呢?所以接下来我们要去实现的函数既要包括静态函数,还要有实例函数。当然,如果要全面一些,我们还要考虑进去虚函数。比如这三个函数:

  1. 问好(SayHi)静态函数
  2. 介绍自己(Introduce)实例函数
  3. 拜年(HappyNewYear)实例函数,虚函数

同时,这两个实例函数还会用到刚才定义的几个成员变量,所以这里我们会引入另一个新的指令ldfld。这个指令是啥意思呢?别忘了堆栈额~所有被操作的数据都需要通过堆栈,所以各位明白了吧?这个指令就是将字段中值压入堆栈中供之后使用的。

首先,我们使用static关键字实现静态函数SayHi:

//问好,静态函数
.method static void SayHi()
{
   .maxstack
ldstr "你好!"
call void [mscorlib]System.Console::WriteLine(string)
   ret
}

然后,我们实现那两个实例函数,与静态函数使用static相反,实例函数使用instance关键字。不过在CIL中类的成员函数默认是实例函数,因此我们可以省略instance关键字。同时,由于是实例函数,因此在获取实例的各个变量之前肯定要先知道该实例的引用。所以每一步取值,我们首先要”ldarg.“将实例的引用压栈,之后再使用”ldfld“去取值压栈。

//实例函数介绍自己
.method void Introduce()
{
.maxstack
ldstr "我叫{0},今年{1}岁,是一个{2}"
ldarg.
ldfld string MurongXiaoPiFu::name
ldarg.
ldfld int32 MurongXiaoPiFu::age
box int32 //对int型的年龄有一个装箱
ldarg.
ldfld string MurongXiaoPiFu::job
call void [mscorlib]System.Console::WriteLine(string, object, object, object)
ret
}
//实例函数,虚函数,过年好
.method public virtual void HappyNewYear()
{
.maxstack
ldstr "过年好"
call void [mscorlib]System.Console::WriteLine(string)
ret
}

OK,这样我们的成员函数就定义完毕了。

实例化之后给各位拜年

好啦,既然我们的慕容小匹夫的类已经定义好了。那么就让我们来实例化一个实例出来给各位拜个年吧。

首先,我们还是使用第一篇文章中写的第一个函数Fanyou来作为我们程序的入口,控制整个流程。那么我们需要在Fanyou函数中声明一个局部变量来存放MurongXiaoPiFu这个类的实例引用。这个变量我们就叫做Murong吧。

.locals init (
class MurongXiaoPiFu Murong)

然后,我们需要实例化一个MurongXiaoPiFu类。所以此处匹夫要引入newobj指令了,newobj指令通过调用类的构造函数来创造一个新的实例。创造好实例之后,这个实例的引用还躺在栈中,所以为了给刚刚才声明的变量Murong赋值,我们需要将栈中的实例引用弹出赋值给Murong,因此需要用到stloc。

newobj instance void class MurongXiaoPiFu::'.ctor'()
stloc Murong

好了,到此我们已经将慕容小匹夫这个类实例化了。那么接下来呢?不错,该调用相应的方法给各位拜年啦!

调用实例的方法

首先我们要调用我们定义的静态函数SayHi。和上面定义成员函数不同而且也很有趣的一点,就是我们使用call指令默认调用的是静态函数。所以我们调用SayHi就变得十分简单了。

//调用静态函数
call void MurongXiaoPiFu::SayHi()

而如果要调用实例函数,则要先将实例的引用压栈,而且在call的后面还需要加上instance。所以我们调用Introduce和HappyNewYear需要分别指定它们的实例,也就是将实例的引用使用ldloc压栈,之后call的时候还需要指明调用的是实例方法。(当然,如果各位有过使用工具将程序集反编译成CIL代码的经历的话。在调用某个实例的方法的部分,各位十有八九看到的可能是callvirt而非call,这个话题本来小匹夫这篇文章也想讨论来着,不过实在有点太晚了。所以留个坑,日后再专门讨论)

//调用实例方法
ldloc Murong
call instance void class MurongXiaoPiFu::Introduce()
ldloc Murong
callvirt instance void class MurongXiaoPiFu::HappyNewYear()

那么编译,再执行的结果如图。

好,其实写到此处也算是一个good ending了。年也给大家拜了,CIL也和大家聊了。 那么~~~

如果各位看官觉得文章写得还好,那么就容小匹夫跪求各位给点个“推荐”,谢啦~

装模作样的声明一下:本博文章若非特殊注明皆为原创,若需转载请保留原文链接http://www.cnblogs.com/murongxiaopifu/p/4296151.html )及作者信息慕容小匹夫

附录

.assembly extern mscorlib
{
.ver :::
.publickeytoken = (B7 7A 5C E0 ) // .z\V.4..
}
.assembly 'HelloWorld'
{
}
.method static void Fanyou()
{
.entrypoint .maxstack
.locals init (
class MurongXiaoPiFu Murong)
newobj instance void class MurongXiaoPiFu::'.ctor'()
stloc Murong
call void MurongXiaoPiFu::SayHi()
ldloc Murong
call instance void class MurongXiaoPiFu::Introduce()
ldloc Murong
callvirt instance void class MurongXiaoPiFu::HappyNewYear() // ldstr "Hello World!"
// call void [mscorlib]System.Console::WriteLine(string) ret
}
.method static void AddLife()
{
.maxstack
//局部变量
.locals init (int32 num1,
int32 num2,
int32 result)
//第一个功能:显示提示输入加数,并获取输入的值
//在屏幕上显示“请输入第一个加数”
ldstr "请输入第一个加数"
call void [mscorlib]System.Console::WriteLine(string)
//获取用户的输入值
call string [mscorlib]System.Console::ReadLine()
//将输入的字符串转化成int
call int32 [mscorlib]System.Int32::Parse(string)
//值出栈,赋给局部变量num1
stloc num1
//num2
ldstr "请输入第二个加数"
call void [mscorlib]System.Console::WriteLine(string)
call string [mscorlib]System.Console::ReadLine()
call int32 [mscorlib]System.Int32::Parse(string)
stloc num2 //第二个功能:相爱相杀,不对,应该是相爱相加...
//将值从变量中压入堆栈
ldloc num1
ldloc num2
//求和
add
//将结果赋值给result
stloc result
//最后一个功能,关键的其实是装箱
//显示的格式
ldstr "{0} + {1} = {2}"
//将num1,num2,result装箱,供之后的writeLine使用。
ldloc num1
box int32
ldloc num2
box int32
ldloc result
box int32
//将算式显示出来
call void [mscorlib]System.Console::WriteLine(string, object, object, object)
ret
} //MurongXiaoPiFu类的声明
.class MurongXiaoPiFu
extends [mscorlib]System.Object //同样System.Object来自mscorlib程序集
{
.field public string name
.field public int32 age
.field public string sex
.field public string job //构造函数.ctor的实现
.method public void .ctor()
{
.maxstack
//定义各个成员变量
ldarg.
ldstr "陈嘉栋"
stfld string MurongXiaoPiFu::name
ldarg.
ldc.i4.s 0x19   //25岁
stfld int32 MurongXiaoPiFu::age
ldarg.
ldstr "男"
stfld string MurongXiaoPiFu::sex
ldarg.
ldstr "程序猿"
stfld string MurongXiaoPiFu::job
ldarg. //1.将实例的引用压栈
call instance void [mscorlib]System.Object::.ctor() //2.调用基类的构造函数
ret
} //问好,静态函数
.method static void SayHi()
{
   .maxstack
ldstr "你好!"
call void [mscorlib]System.Console::WriteLine(string)
ret
} //实例函数介绍自己
.method void Introduce()
{
.maxstack
ldstr "我叫{0},今年{1}岁,是一个{2}"
ldarg.
ldfld string MurongXiaoPiFu::name
ldarg.
ldfld int32 MurongXiaoPiFu::age
box int32 //对int型的年龄有一个装箱
ldarg.
ldfld string MurongXiaoPiFu::job
call void [mscorlib]System.Console::WriteLine(string, object, object, object)
ret
}
//实例函数,虚函数,过年好
.method public virtual void HappyNewYear()
{
.maxstack
ldstr "过年好"
call void [mscorlib]System.Console::WriteLine(string)
ret
}
}

用CIL写程序:定义一个叫“慕容小匹夫”的类的更多相关文章

  1. 用CIL写程序:从“call vs callvirt”看方法调用

    前文回顾:<用CIL写程序系列> 前言: 最近的时间都奉献给了加班,距离上一篇文章也有半个多月了.不过在上一篇文章<用CIL写程序:定义一个叫“慕容小匹夫”的类>中,匹夫和各位 ...

  2. 用CIL写程序:你好,沃尔德

    前言: 项目紧赶慢赶总算在年前有了一些成绩,所以沉寂了几周之后,小匹夫也终于有时间写点东西了.以前匹夫写过一篇文章,对CIL做了一个简单地介绍,不过不知道各位看官看的是否过瘾,至少小匹夫觉得很不过瘾. ...

  3. 用CIL写程序:写个函数做加法

    前言: 上一篇文章小匹夫为CIL正名的篇幅比较多,反而忽略了写那篇文章初衷--即通过写CIL代码来熟悉它,了解它.那么既然有上一篇文章做基础(炮灰),想必各位对CIL的存在也就释然了,兴许也燃起了一点 ...

  4. 简单练习题2编写Java应用程序。首先定义一个描述银行账户的Account类,包括成员变 量“账号”和“存款余额”,成员方法有“存款”、“取款”和“余额查询”。其次, 编写一个主类,在主类中测试Account类的功能

    编写Java应用程序.首先定义一个描述银行账户的Account类,包括成员变 量“账号”和“存款余额”,成员方法有“存款”.“取款”和“余额查询”.其次, 编写一个主类,在主类中测试Account类的 ...

  5. 慕容小匹夫 Unity3D移动平台动态读取外部文件全解析

    Unity3D移动平台动态读取外部文件全解析   c#语言规范 阅读目录 前言: 假如我想在editor里动态读取文件 移动平台的资源路径问题 移动平台读取外部文件的方法 补充: 回到目录 前言: 一 ...

  6. 编写Java应用程序。首先定义一个描述银行账户的Account类,包括成员变 量“账号”和“存款余额”,成员方法有“存款”、“取款”和“余额查询”。其次, 编写一个主类,在主类中测试Account类的功能

    package com.hanqi.test; //银行账号 public class account { private String zhanghao;//账号 //私有余额 private do ...

  7. 3.编写Java应用程序。首先定义一个描述银行账户的Account类,包括成员变 量“账号”和“存款余额”,成员方法有“存款”、“取款”和“余额查询”。其次, 编写一个主类,在主类中测试Account类的功能。

    Account package com.hanqi.test; public class Account { private String zhanghao;private double yve; A ...

  8. 微信小程序生成小程序某一个页面的小程序码

    1 登录微信小程序后台,mp.weixin.qq.com 2 点击右上角工具->生成小程序码 3 填写小程序名称或appid 4 关键一步,下面页面填写用户微信号后,打开小程序到某一个页面,点击 ...

  9. svg如何用marker 定义一个黑色的小圆点

    <defs> <marker id="markerStartArrow" viewBox="0 0 30 30" refX="10& ...

随机推荐

  1. 关于解决python线上问题的几种有效技术

    工作后好久没上博客园了,虽然不是很忙,但也没学生时代闲了.今天上博客园,发现好多的文章都是年终总结,想想是不是自己也应该总结下,不过现在还没想好,等想好了再写吧.今天写写自己在工作后用到的技术干货,争 ...

  2. Logstash实践: 分布式系统的日志监控

    文/赵杰 2015.11.04 1. 前言 服务端日志你有多重视? 我们没有日志 有日志,但基本不去控制需要输出的内容 经常微调日志,只输出我们想看和有用的 经常监控日志,一方面帮助日志微调,一方面及 ...

  3. 在docker中运行ASP.NET Core Web API应用程序(附AWS Windows Server 2016 widt Container实战案例)

    环境准备 1.亚马逊EC2 Windows Server 2016 with Container 2.Visual Studio 2015 Enterprise(Profresianal要装Updat ...

  4. WPF 有用博客地址

    增加智能感知的RichTextBox扩展控件(WPF) WPF自定义控件与样式(3)-TextBox & RichTextBox & PasswordBox样式.水印.Label标签. ...

  5. php报错 ----> Call to undefined function imagecreatetruecolor()

    刚才在写验证码的时候,发现报错,然后排查分析了一下,原来是所用的php版本(PHP/5.3.13)没有开启此扩展功能. 进入php.ini 找到extension=php_gd2.dll ,将其前面的 ...

  6. iOS 小知识点(持续更新)

    1.如何通过代码设置Button  title的字体大小 设置Button.titleLabel.font = [UIFont systemFontOfSize:<#(CGFloat)#> ...

  7. 参数探测(Parameter Sniffing)影响存储过程执行效率解决方案

    如果SQL query中有参数,SQL Server 会创建一个参数嗅探进程以提高执行性能.该计划通常是最好的并被保存以重复利用.只是偶尔,不会选择最优的执行计划而影响执行效率. SQL Server ...

  8. Jenkins的一个bug-同时build一个项目两次导致失败

    我们有一个job A, A只是配置了一些参数,它会去触发模板job B. 我一开始点击构建A, 马上发现参数配置不对,于是撤消了构建,但是我没有发现B已经被触发,我重新配置参数,然后再次构建A,这个时 ...

  9. 使用Metrics.NET 构建 ASP.NET MVC 应用程序的性能指标

    通常我们需要监测ASP.NET MVC 或 Web API 的应用程序的性能时,通常采用的是自定义性能计数器,性能计数器会引发无休止的运维问题(损坏的计数器.权限问题等).这篇文章向你介绍一个新的替代 ...

  10. 让ASP.NET5在Jexus上飞呀飞

    就在最近一段时间,“Visual Studio 2015 CTP 5”(以下简称CTP5)发布了,CTP5的发布不仅标志着新一代的VisualStudio正式发布又向前迈出了一步,还标志着距离ASP. ...