文档章节

.Net开发笔记(八) 动态编译

IT周见智
 IT周见智
发布于 2015/06/05 17:18
字数 2498
阅读 6
收藏 1

虽然标题为“动态编译”,其实本文包含两个部分,一个就是标题说的动态编译源代码,另外一个应该是动态生成源代码。也就是说,在.Net中可以存在这样一种情况:在程序运行期间,由程序控制动态生成一份源代码(Source Code),然后再编译该源代码,生成一个新的程序集,紧接着再加载生成的程序集,最后运行。这个情况可能出现在很多地方,比如一些模板代码生成,像asp.net页面生成html,还可以在程序运行期间,由用户控制,动态的扩展程序功能。

要了解以上的实现过程,需先了解以下几个知识点:

  1. 反射
  2. 跨应用程序域(AppDomain)访问支持
  3. CodeDOM(代码文档对象模型)

我大概介绍一下后面两个,反射我就不说什么了,想必诸位都清楚,文章最后我会展示一下我做的一个Demo,主要有两个功能,动态编译代码和仿aspx页面生成html,功能很简单做工也很粗糙,但大概的原理过程都有,提供原程序下载,源程序注释都很详细,下下来就能看懂。

AppDomain(应用程序域):

先引用MSDN上的一句话:应用程序域(由 AppDomain 对象表示)为执行托管代码提供隔离、卸载和安全边界。是个什么意思呢?说得概括直白一点,就是它相当于一个容器,存放一些数据和其他资源,每个AppDomain都有自己的范围,各管各的事情,我们在将源代码(Source Code)动态编译成程序集后,还需要把它加载,最后运行,不然的话,动态编译就没什么意义了,想必各位清楚,在咱们学习“反射”的时候就知道用Assembly.Load()类似这样的方法加载程序集,然后创建实例(Instance),然后取得它的Type进行操作等等,但是如果按以上这样的方法加载的程序集的话,它默认被加载到当前AppDomain中(此当前AppDomain就是指程序一开始运行系统自动为其创建的一个AppDomain),又由于程序集不能单独卸载,也就是说加载进来之后的程序集会一直存在该AppDomain中,你如果加载的次数不多,这也就无所谓,但是如果像我们今天遇到的情况,需要反复不停的编译源代码,将生成的程序集加载运行的话,问题就出现了,最后加载进来的程序集就会越来越多,最后内存不足(Leak Memory),解决的办法就是,将每次用完的程序集卸载掉,目前卸载程序集的办法只有(我了解到的)卸载程序集所在的AppDomain,容器玩完了,容器里面的东西肯定也玩完了,但程序集如果一开始就加载到当前AppDomain中的话,问题就来了,因为当前AppDomain是不能被卸载的(如果当前AppDomain卸载了,什么都玩完了,怎么会有后续),所以,一开始加载程序集的时候,就不应该将其加载到当前AppDomain,这就需要我们新创建一个AppDomain,专门用来存放加载进来的程序集,一旦使用完毕后,马上将其卸载。创建新的AppDomain、跨域访问等具体操作,请看源代码。

CodeDOM(代码文档对象模型):

如果诸位知道DOM的话,这个其实很好理解,它两意思差不多,我们在处理html代码(XML)的时候,将它的各个节点都当做一个“对象”(Object),<Body></Body>、<Title></Title>等等,都是对象,在构建一个html文档的时候,我们可以先创建根,然后创建它的各个子节点,再然后设置各个子节点的属性、文本什么的,打个比方:HtmlHTML html = new HtmlHTML();HtmlHEAD head = new HtmlHead(); html.Childs.Add(head);创建两个节点对象,将一个加入到另外一个的子节点中去。.Net中System.Xml命名空间中有很多操作XML的类型,比如XmlAttribute、XmlElement、XmlComment等等,每一个类都代表XML文档中的一个节点元素,构建一个xml文档的时候,也是将一个加到另外一个的子节点集合中去,然后设置各个元素的其他属性等等,大概操作基本都这样。网上摘抄一段代码如下:

View Code
 1 XmlText xmltext; 
 2          XmlDocument xmldoc = new XmlDocument(); 
 3 
 4         //加入XML的声明段落 
 5          XmlNode xmlnode = xmldoc.CreateXmlDeclaration("1.0", "gb2312", null); 
 6          xmldoc.AppendChild(xmlnode); 
 7 
 8         //加入一个根元素 
 9          XmlElement xmlelem = xmldoc.CreateElement("", "bookstore", ""); 
10          xmltext = xmldoc.CreateTextNode(""); 
11          xmlelem.AppendChild(xmltext); 
12          xmldoc.AppendChild(xmlelem); 
13 
14         //加入一个子元素 
15          XmlElement xmlelem1 = xmldoc.CreateElement("", "book", ""); 
16          xmltext = xmldoc.CreateTextNode(""); 
17          xmlelem1.AppendChild(xmltext); 
18         //为子元素"book"增加两个属性 
19          xmlelem1.SetAttribute("genre", "", "fantasy"); 
20          xmlelem1.SetAttribute("ISBN", "2-3631-4"); 
21          xmldoc.ChildNodes.Item(1).AppendChild(xmlelem1);

如上所述,构建一个xml文档的代码。那么,CodeDOM就可以类比了,把什么命名空间(namespace)、类(Class)、引用(using)、方法(Method)、事件(Event)、赋值语句(AssignStatement)、异常捕获(TryCatchFinallyStatement)等等都当做对象,如果你仔细观察,发现其实程序源代码(Source Code)的结构跟html、xml等都是一样一样的,一层嵌套着另外一层,既然一样,那么我们构造一个源程序文档的方法肯定也是和构造DOM结构文档一样。

另外,像XmlAttribute、XmlComment、XmlNode这样的类型存在System.Xml命名空间中,那么CodeDOM中的各个类型存在哪儿呢?.Net中为我们提供的这些类型在System.CodeDOM这个命名空间中,基本上源程序中各个结构都能在这个命名空间中找到对应的类型。

到此,我们知道了利用System.CodeDOM中的类型动态的构造源代码,那么怎么将构造完的源代码保存下来(生成文本格式的代码,保存成源文件)?以及怎么将其编译,生成最终可以运行的程序集?.Net中又为我们提供了专门用于生成源代码和编译源代码的类,它们存在System.CodeDOM.Compiler命名空间中,具体要使用其中的两个接口ICodeCompiler和ICodeGenerator,它们两个的C#和VB版的具体实现类存在Microsoft.CSharp命名空间和Microsoft.VisualBasic命名空间。生成源代码和编译源代码操作都会返回执行结果,可以在返回结果中查看相关信息,比如编译源代码时,可以查看编译错误。

现在我们已经清楚了整个流程,来张图说明一下:

图1

做的一个Demo

做的这个Demo中其实并没有使用到上图中的第2步和第3步,因为不需要动态生成源码,取而代之的是让用户直接输入源码,然后将输入的源码拿来编译执行,或者先将aspx页面代码(html和C#的混合体)解析成代码片段,然后拿来编译执行。也就是说,Demo中的流程应该是这样的:

图2

源码注释很详细,我就在这不贴了。再来两张效果图:

图3

图4

图5

源码C#部分XP .net3.5测试通过,VB部分没测试。

下载地址:http://download.csdn.net/detail/xiaozhi_5638/5172507

希望有帮助O(∩_∩)O~,如果觉得本文对您有用,请点击一下右下方的“赞”或者给个评论建议,非常感谢!!!

题外话:

    昨晚上跟b哥谈论一件衣服,一起回忆起了我大学时期的着装,无袖T恤+长短裤+ 人字拖,现在想想,甚是现世,不知不觉惭愧起来,责备自己年少不懂事,乱了校 规伤了风化。高三时,穿过一段时间的木屐+大裤衩,不知wy君是否还记得起?自认为学生时代没有给老师留下好的印象,学习成绩即不很突出,也不是老师眼中的乖乖娃,高中上语文时看物理,上物理课时看英语,被老师发现无数次,没办法,后来我一跟别人说我是偏科生,问哪科,答曰数学,无比脸红,丢尽了无数理科生的 脸,后来高考马马虎虎,读了一个二流大学,大一规规矩矩,大二开始翘课抄作业 ,即影响校容也丢了现代青年应有的颜面,谈来惭愧,不提也罢。有时候在讲一个 人过去是怎么“作恶多端”,并不是想衬托TA现在有多大进步,或者炫耀TA有一个 多么“拉风”的青春,这就像现在许多人讲自己出生如何贫寒,起跑线比别人多么 靠后,条件如何艰苦,但TA还是一直会坚持下去,他相信自己是个潜力股,说到这,无比骄傲,其实殊不知,出生贫寒并不是优势,起跑线落后更不是资本, 你说你是“潜力股”,吁,“潜力股”好多时候也许只是安慰那些有着弱小心灵的屌丝,不是吗,跑题了。其实我本人倒是一直推崇做事做完了就完了,不要总是回过头 去想它对不对,更要避免耿耿于怀,但学生时代的所作所为,有时候的确让我不安 ,觉得愧对各位老师,也许他们根本记不起,但还是在此道个不是,虽然我现在并 不是老师,也没有身兼“传道授业解惑者”这样的职业。《单车》中有句歌词“经 已给我怎会看不到,虽说演你角色实在有难度”,虽然角色不同,意义大概一样吧 。

© 著作权归作者所有

共有 人打赏支持
IT周见智

IT周见智

粉丝 10
博文 61
码字总数 185891
作品 0
西青
Cocos2dx游戏开发系列笔记9:android手机上运行《战神传说》,并解决横竖屏即分辨率自适应

上节说到cygwin下成功编译出so文件,下面我们要把游戏运行在android上。 开始干活! 其实步骤可以参考 Cocos2dx游戏开发系列笔记6:怎样让《萝莉快跑》的例子运行在vs和手机上 1 用eclipse打...

懒骨头
2013/12/09
0
0
大前端 Android 开发日记七:MPAndroidChat 填坑笔记

大前端 Android 开发日记七:MPAndroidChat 填坑笔记 Blog | Phodal Geek's Life2017-12-241 阅读 Android 继续上一天的 MPAndroidChat 填坑记录。 MPAndroidChat 自定义 Marker 首先,是自定...

Blog | Phodal Geek's Life
2017/12/24
0
0
Android-开发姿势

Android 攻城狮—全套必备神级工具(开发,插件,效率) 如果你有用的酸爽的软件,请下面留言。我会持续更新。。。 “替你” 总结的 Gradle 配置 关于 Gradle 配置的一些总结,欢迎交流! 常...

掘金官方
2017/12/28
0
0
大前端 Android 开发日记八:Android 短信、微信、微博分享

大前端 Android 开发日记八:Android 短信、微信、微博分享 Blog | Phodal Geek's Life2017-12-241 阅读 Android 在纠结了几天的图表功能之后,我开始开发一个新的功能。即分享内容到短信、微...

Blog | Phodal Geek's Life
2017/12/24
0
0
KVM虚拟化学习笔记系列文章列表

kvm虚拟化学习笔记(一)之kvm虚拟化环境安装 http://koumm.blog.51cto.com/703525/1288795 kvm虚拟化学习笔记(二)之linux kvm虚拟机安装 http://koumm.blog.51cto.com/703525/1289627 kvm虚拟...

蓝狐乐队
2015/03/19
0
0

没有更多内容

加载失败,请刷新页面

加载更多

八月新增开源项目:假装自己是图形界面的 Git 命令行工具

每月新增开源项目。顾名思义,每月更新一期。我们会从社区上个月新收录的开源项目中,挑选出有价值的、有用的、优秀的、或者好玩的开源项目来和大家分享。数量不多,但我们力求推荐的都是精品...

编辑部的故事
16分钟前
4
0
20180918 find命令与Linux文件扩展名

命令find 用来查找搜索文件。 搜索文件相关命令: which 从环境变量里的目录中去搜索 whereis(不常用) 从一个固定的库中搜索 locate(需要单独安装 yum install -y mlocate) 查询时会从/var/...

野雪球
19分钟前
1
0
一步步编写自己的PHP爬取代理IP项目(二)

这一章节我们正式开展我们的爬虫项目,首先我们先要知道哪个网站能获取到免费代理IP,目前比较火的网站有西刺代理,快代理等,这里我们拿西刺代理作为例子。 西刺代理官网: http://www.xic...

NateHuang
39分钟前
1
0
GO 数组相关操作

package mainimport("fmt""math/rand""time")func main() {//数组的几种定义方式var arr1 [3]int = [3]int{1,2,3}var arr2 = [3]int{4,5,6}arr3 := [3]string{"h", "w", ......

汤汤圆圆
今天
1
0
JAVA 中interrupt、interrupted和isInterrupted的区别

首先,我们说明下三个方法的功能 interrupt() 向当前调用者线程发出中断信号 isinterrupted() 查看当前中断信号是true还是false interrupted() 是静态方法,查看返回当前中断信号并将中断信号...

我爱春天的毛毛雨
今天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部