把C++当脚本语言写!

  提到脚本,脑海里马上闪过一大堆:Python,Perl,Ruby,PHP,JS,VBS,LUA。。。 不过你有没听说过,用经典的C++做脚本语言吗?先不多说,上个图。(先别纠结那个function,那仅仅是个宏而已,待会你就明白了)

  或许你在想这一定是疯了,用世界上最复杂的语言做脚本,写的人累不说,脚本引擎先累坏了。各种复杂的模板库,要边解释边运行,得有多强大的虚拟机才撑得住。

  好吧,那么我们退一步,不强求解释执行,回归到原始的编译后执行。———— 不过那还算脚本吗?

编译速度

  事实上如今高性能的脚本都是先编译后运行的,大名鼎鼎的JavaScript V8引擎,号称速度最快的LUA-Jit,以及众所周知的ActionScript。。。预先编译不仅能大幅提高运行速度,更重要的是能够提前发现脚本中显式的错误。

  但脚本中所谓的编译,和传统语言的编译,还是很大区别的。脚本的编译,不过是代码上的深度优化,很快就可以完成。相比复杂了多的C++来说,似乎是望尘莫及的。提到C++的编译速度,大家的映象莫过于在VC里按下F5之后,看着输出框内一条一条的“Compiling…”缓缓出现。有时仅仅测试一个微小的修改,也要等上好几秒的时间。缓慢的编译速度备受煎熬,以至于简单的程序往往选择VB或C#这样可以快速调试的语言。

  对于庞大的MFC程序来说,缓慢的编译是理所当然的。但简单的小程序出现过长的编译时间,那一定是头文件引用的不合理了。事实上,使用预处理头文件的小程序,编译仅仅是一瞬间的,之后的各种停顿往往是IDE引起的。

  那么我们就来测试下,不用IDE,仅用纯命令编译个C++小程序。我们使用VC6.0的编译器:CL.exe

  为了确保纯净的编译环境,我们把CL.exe必须依赖的文件复制到新建的文件夹里。对于VC6的版本,只要有如下5个文件,就可以完成.cpp到.exe的编译了。

CL.exe
  C1XX.DLL
  C2.DLL
  MSPDB60.DLL
Link.exe

  打开cmd,设置好环境变量,对应到VC6的头目录和库目录

SET INCLUDE=C:\Program Files (x86)\Microsoft Visual Studio\VC98\Include

SET LIB=C:\Program Files (x86)\Microsoft Visual Studio\VC98\Lib

  就可以调用命令编译了:

cl test.cpp

  一眨眼的工夫,编译和链接完成,生成了test.exe,一切正常。而这还是在没有使用预编译头的情况下编译的。

  由此可见,即使语言本身很复杂,但只要用它写的代码不复杂,编译还是非常快的。

  仔细想想也应如此。以如今的硬件配置,运行98年的编译器,编译一个才几行代码的程序,自然是一瞬间的。

  命令行编译简单的C++程序是如此的快速,利用这个优势,继续我们的脚本探索。。。

运行环境

  如果要写一个生成100个随机序列号的小程序,你会使用哪类语言?

  相比传统语言要先创建一个工程项目,我们直接在桌面新建个文本文件就可以写脚本了。

  虽然用文本编辑器写代码没任何优势,但对于简单的程序足矣。之后程序交给其他人使用时,脚本优势就淋漓尽致的体现出来了:当他们自己想简单修改一些逻辑规则时,只需用记事本打开就可以,而记事本每台电脑上都有。

  相反,传统语言写的程序,即使有源代码,用户想简单的修改下也无法生效,还需安装并配置好相应的开发环境才行,这对不熟悉的人来说颇费周折。

  所以脚本必须足够简单 —— 简单到用户只管修改和运行就可以,其他步骤都交给脚本宿主自动完成。

  如果想用C++写脚本,那么代码的编译和链接当然必须是全自动的,这并不复杂。

  但仅仅依靠CL.exe等几个命令还是不够的,因为在其他的电脑上并没有相应的开发环境 —— Include和Lib文件夹,因此就无法通过编译和链接了。

  而这些头文件库文件,一共多达上千个,全都带上则有近百兆!显然,我们的脚本只用到几个基本功能就可以了,那些复杂的windows头文件就没必要了。

  事实上,程序的头文件只是函数和结构的定义,仅仅用来给编译器分析而已,最终并不生成实际的指令。所以,我们把常用的头文件,事先生成一个.pch预编译头文件就可以。以后编译时,将他对应到某个头文件就可以了,例如stdafx.h。这样就无需使用任何头文件了。即使stdafx.h也不在,编译仍然能通过,因为这一切都打包在.pch里面了。并且大量的头文件经过事先的分析,编译时就无需再编译它们了,速度大幅提升。

  至于Lib文件,里面都是库函数的内容。除非整个程序不使用任何C运行时库,那么我们可以不带上任何lib,但那样只能写最基本的代码了。对于一般的简单脚本程序,只需几个必要的lib即可:KERNEL32.LIB,LIBCMT.LIB,LIBCPMT.LIB,OLDNAMES.LIB。总共才1M多。

  我们把这几个lib文件以及.pch文件,放在cl.exe同个目录下,这样就无需指定INCLUDE和LIB环境变量。

  至此,我们有了一个精简版的VC6编译器。通过上述10个文件,我们可以不依赖任何环境,独立编译C++程序了。

cl /Yu”stdafx.h” /Fp”MyDLL.pch” test.cpp

实际运行

  现在,我们可以动态产生C++代码文件,并且自动编译的能力了。但是如何将最终的二进制文件与脚本宿主交互呢?

  由于exe只能运行在独立的进程里,数据交互只能通过匿名管道,要实现回调什么的非常困难。

  但若换成dll就可以大显身手了,不仅运行在同一进程空间内,更重要的是dll是可以动态加载卸载的,这一点太符合脚本程序的特性了。并且当某个模块更新了之后,就可以把先前的模块释放掉,加载最新的。而这一切都是动态的,无需重启宿主即可完成!

  而且dll可以导出内部的函数,宿主用GetProcAddress()就可以轻松获得某个函数地址;至于回调,传递一个宿主的函数指针给脚本就可以了。只要约定好函数声明,双方都可以用最简单原始的方法互相调用,甚至共享同一块内存空间。

  为了让函数导出更简洁,本例中定义了个叫function的宏:

define function extern “C” __declspec(dllexport) void

  于是就可以简单的定义一个导出函数了:

function Test()
{
// some code here
}

  是不是很有脚本的感觉呢:)

语法检查

  一个用文本编辑器敲出来的代码,拼写错误是难免的。所以一个好的脚本引擎,会在运行前做一次全面的语法检查,事先排除明显的错误,而不是边解释边运行。

  C++就是将其做到了极限,不仅能查出致命的错误,甚至不规范的代码也会有警告提示。这是非常值得的,一个小bug浪费的时间,足够几万次编译了。

  想要在我们的C++脚本里实现这个功能,其实是非常简单的。因为在调用cl.exe编译时,要是有编译错误就会反馈出来。我们根据对应的错误行号,提示用户就可以了。

 
调试环境

  一个强大的脚本引擎,往往带有调试器。虽然编译器能够预先排除一些错误,但是逻辑上的错误只有在运行时才能出现。

  对于简单的脚本程序,这项功能似乎不那么重要。毕竟在调试状态下运行,性能会有所影响。

  在C++脚本里,我们可以通过宏来扩展调试功能,决定是否输出调试信息。不过对于异常错误,处理就比较讲究了。

  由于我们最终运行的是二进制dll模块,这和普通的脚本有着天壤之别。dll模块是和宿主共用一个进程的,所以一旦当dll内异常触发时,整个进程包括宿主一块进入调试状态了(系统装有开发环境的话)。如果错误过于严重,会导致整个进程的崩溃。这是个非常值得注意的地方,也是C++作脚本在权限上的隐患。所以尽可能少用指针特性,使用更安全的代码,让代码风险降到最少。

  对于致命的错误,宿主记录下dump文件是非常重要的,方便调试。

  不过出于简单,本例的宿主是用VB写的,也就无法在调用前使用__try{}进行SEH捕捉。如果宿主也是C++实现的话,则尽可能捕捉dll内的异常。

开发环境

  有别于脚本语言,C++本身就是用于大型程序的开发,所以开发环境是非常完善的。

  但作为一个脚本,往往都是单个的文本文件,而不是一个项目组。任何版本的VC编辑单个cpp文件,和编辑纯文本文件几乎没有区别。因此我们事先得建立一个模板项目,将需要编辑的cpp移到此项目内开发,这样才会有下拉框智能提示等功能。

  不过既然选择它作为脚本来使用,那就应该用来处理一些简单的,经常变更的逻辑事务。对于复杂的脚本程序,还不如直接写在宿主里面了。

  事实上,“程序”和“脚本”之间从没一条固定的界限。用纯粹的程序也可以写一个复杂的游戏故事情节,用纯粹的脚本也可以开发一个大型项目。只不过太过死板,或太过灵活,都会增加额外的工作量。

总结

  与其称之为C++脚本,倒不如说是插件———可以根据需求,动态产生指令的插件。

  虽然可以玩转出一些脚本的特征,然而C++终究是门严格的语言。相比脚本的灵活性,C++固然更为严谨和死板。当然,凭借强大的宏、模版、运算符重载,我们可以充分扩展,为脚本提供丰富多样的特征和语法糖。

  当然,它的优势也是显而易见的:性能超高,交互简单。并且完全支持C++的特性。

  事实上,不仅仅是C++,任何一门高级语言都可以当“脚本”使用,只要调用它们的编译器即可。如果喜欢C#,或者Java风格,只需稍作修改就可以。

  为了简单演示,本例使用VB写了个简单的宿主程序,包括基本的编译,链接,加载,语法检查功能。

  宿主提供了一个叫“Console”的接口,可以输出字符串。要实现更多接口和扩展功能,修改cl文件夹内的T.h即可。

  源码可以在这里下载:http://files.cnblogs.com/index-html/CppScript.rar

  其中有一个DLLTmpl的工程,没有任何用处,仅仅为了生成一个.pch预编译头文件而已。如果想在脚本里使用更多的头文件,就得在StdAfx.h内添加。编译之后的release/MyDll.pch复制到cl文件夹,覆盖原有的即可。

后记:

当初写这篇文章,是优化了一个防火墙数据包过滤系统的心得。因为数据包数量非常大,如果每次都通过传统的脚本判断,性能开销较大;如果写在 DLL 之类的模块里,虽然性能很高,但每次修改规则都得重新发布一次,很麻烦。所以最后把规则写成纯文本的 C++ 代码,程序启动时自动将其编译成 DLL,中途代码若有修改,也可以热更新。这样性能和配置都可兼得。当然,有条件的话做成 JIT 系统是最好的,例如 winpcap 过滤器那种。不过出于成本,直接调编译器是最简单的。而且套用现成的语言,学习成本也很小。(后来 C++0x 出来了,有一个 auto 关键字,可以类型自动推断。所以还可以 #define var auto,那样就更像 javascript 脚本了:)最近网上看到篇文章 《什么是“脚本语言”》,终于把之前想说的都说出来了,所以特来更新下。(2015/06/26)

把C++当脚本语言写相关推荐

  1. php编写服务器端脚本程序,PHP脚本语言写的简单服务器程序

    $username = $_POST["username"];/*客户端请求方式为POST,请求参数封装成nsdata类型放在HTTPBody中传给服务器,服务器用PHP脚本语言接 ...

  2. 如何用c需要写游戏脚本语言,写游戏脚本最好的语言用运行比较好

    有的窗口的标题是比较容易得到的,所以应使用标题进行搜索" '日期相关: 1. 取出日期时间 1)DateValue(x),TimeValue(x) 格式:v=DateValue(x) :取出 ...

  3. 是时候好好去学门脚本语言了

    根据我观察我周边的一些.net和C++程序员,我发现一件很有趣的事情.当遇到一些复杂问题的时候,比如说需要分析一大批数据,不同的人会选择不同的方法.有一些人会选择使用自己熟悉的编程语言去写一个一次性的 ...

  4. fceux源码解析_FCEUX金手指加强版 - 使用Lua脚本语言编写FC/NES金手指脚本

    一直觉得大部分的FC/NES模拟器的作弊码金手指不是那么方便使用, 比如魂斗罗1代, 玩家的武器可以通过修改0xAA的值来改变: 0x11为M弹(重机枪),0x12为F弹(圈圈),0x13为S弹(散弹 ...

  5. [大二上]什么是脚本?什么是脚本语言?

    将某技术讲解到让非专业也可以理解,才是真的学会了 什么是脚本? 脚本:scirpt 是一种特定的描述性语言, 依据一定的格式编写的可执行文件 源自: 原来是编剧术语, 是故事发展大纲的意思, 用以确定 ...

  6. 第9章 EBASIC脚本语言及应用

    到目前为止,我们设计并实现了一个完整的计算机系统,包括8051计算机硬件.51DOS磁盘操作系统和基本命令程序,但为其开发应用程序只能在PC上进行,还需要Keil开发环境下和C语言,能不能直接在我们自 ...

  7. python怎么写测试脚本语言_手把手带你,用Python写一个Monkey自动化测试脚本!!!...

    一.为什么需要一个测试脚本? 之前讲解了 Android Monkey 命令的使用方式,今天趁着还热乎就手把手用 Monkey 写一个压力测试的脚本.还不了解什么是 Monkey 的,可以看看之前的文 ...

  8. php 课程节次周次提取,用PHP写一个最简单的解释器Part5(计算器最后一节,下节开始如何写个脚本语言)...

    经过几天的努力,用PHP已经实现了一个完整的基础计算器,如下图 上代码 define('ISINTEGER','ISINTEGER');//定义整数类型描述 define('PLUS','PLUS') ...

  9. python自动化测试脚本怎么写_自动化测试脚本一般用什么语言写

    1. 自动化测试脚本一般用什么语言写 我主要是做web测试的,最近也在研究自动化测试. 自动化测试主流的工具应该是QTP吧,这个你可以去51testing论坛搜到很多相关的资料.QTP是一个商业软件. ...

最新文章

  1. java Graphics2D类
  2. php 表格分页代码,[Php]分页及表格样式
  3. Element-ui导航组件NavMenu导航高亮设置
  4. Java-idea-生成for循环
  5. 机器学习算法在用户行为检测(UBA)领域的应用
  6. Python - shutil模块(2)——压缩目录、文件
  7. solver.prototxt参数解析
  8. 软件测试论坛_进阶测试攻略——价值驱动的软件测试
  9. ubuntu安装nvidia显卡驱动+cuda+cudnn
  10. 量子计算机物理学,使用量子计算机来测试物理学的基本原理
  11. 微信小程序使用node-xlsx解析excel文件的云函数
  12. 部署gitlab+gitrunner+nexus镜像仓库CI流水线配置
  13. 【黑马Java笔记+踩坑】Maven高级
  14. 今日参与会议后的感悟
  15. GPIO的八种工作模式
  16. 睿智的法杖v8.28ti
  17. java 后台开发中model与entity(实体类)的区别
  18. 山西师范大学现代文理学院计算机一级试题,山西师范大学现代文理学院2007—2008学年第一学期期末考.doc...
  19. 开放式办公室不利心理健康:噪音大且效率低
  20. taotao.sql文件(免积分分享)

热门文章

  1. linux宝塔面板硬盘文件转移,Linux宝塔面板系统盘迁移数据到数据盘www
  2. 邀请新用户奖励现金活动的一些反思
  3. MySQL性能分析工具的使用:慢查询日志、EXPLAN的使用、分析优化器执行计划:trace、MySQL监控分析视图-sys schema
  4. 页面渲染中的onload 事件
  5. unity Animator 添加事件
  6. python endswith函数_Python startswith()和endswith() 方法原理解析
  7. WPF界面开发第三方控件入门指南——菜单项
  8. php 自定义行间距,css文字行间距怎么设置?css设置行间距方法
  9. Python使用opencv 打开摄像头
  10. 小熊U租完成1.5亿元Pre-B轮融资,加速智慧办公生态布局