All Stories

使用SWIG扩展EXE内嵌入的脚本解释器

  虽然几年前就知道SWIG了,但一直以来都没真正用过,又是一次叶公好龙。  今天在公司里跟同事偶然谈起项目里嵌入的Ruby解释器的问题,于是回来兴致高涨,决定研究继续研究一下CryptTool遗留下来的问题。  首先介绍一下CryptTool。这是一个可以使用多种算法计算文本或文件内容的hash值的小工具,最早的原型是几年前还在大学时写的一个计算文件md5的小程序,去年做一体化平台时,有一项内容是要用一个单向散列值来近似唯一地标识一个文件,所以顺便找了一个几种常用的hash算法源代码,包括md2/4/5、SHA1/224/256/384/512、haval3/4/5、CRC、GHash、Gost3/5、RMD128/160/256/320、Adler32、FCS等。回家用MFC写了一个,完成了多种算法计算的功能,当时突然兴起,觉得可以嵌入脚本解释器,实现外部插件扩展。结果只是实现了搜索指定目录下的脚本文件,并添加相应的项到主菜单中,通过菜单项激活脚本执行。连脚本执行都没实现,因为当时贪心,又觉得好玩,打算把Python、Ruby、Lua都嵌入进来,确实都链接进来了,却没有实际的功能。  再简单介绍一下SWIG。早先,我只知道它是用于嵌入和扩展脚本解释器的,但具体详细的作用却并不清楚。现在,我有了新的认识,SWIG主要作的是扩展脚本解释器时,将C++代码做一层封装,实现自动向脚本解释器注册相关的信息(如函数、变量、模块等等)。说起脚本解释器的扩展,大多数的资料,甚至连SWIG的帮助文档里,都是说的将C++代码编译生成动态链接库,然后官方的脚本解释器会载入该动态链接库,并在解释执行脚本时使用动态链接库中的内容。而我现在的需求,前面已经略有提及,是把我自己的exe程序里嵌入的脚本解释器扩展了,并且我要的是实现该扩展功能的代码是exe的一部分,而不是独立的动态链接库。两年前,用C++ Builder写过一个用于数据处理的小程序,其中就花了不少精力实现了嵌入Python、TCL、Lua,并扩展了几个函数,当时全部都是通过手工编写代码,按照标准的官方推荐通用作法实现,以至于连续近2个月几乎每天都是后半夜2点才睡,到后来精疲力竭,连上WC掀马桶盖都觉得异常吃力。  现在用VC实现嵌入和扩展理论上应该也不会有多少差别,只不过扩展部分是用SWIG完成的。先写一个后缀为.i的文件,定义扩展的模块名和扩展的函数原型,如果有变量也写上。然后在命令行中运行SWIG,用法行简单,命令行参数也很简单,因为是放在MFC工程中,所以理所当然地把输出限定为C++类型的代码。这里值得提一下的是,指定输出的文件名,用.h文件比较合适,它里面是一堆函数的实现,但并没有单独的原型声明,所以在实际工程中,只要直接include该文件就行,而不是当成源代码.cpp添加到工程,不然编译会比较麻烦。另外还有一点是,写在.i文件中的变量类型,不要用宏定义,而是用实际的标识法,开始我用LPCSTR和LPSTR分别表示const char *和char *,结果在脚本调用时,就会报类型不匹配,直接写成const char*和char *就没有问题了。再有一点,SWIG生成的代码中,会有一个初始化的函数,该函数完成各个扩展的登记注册功能,在EXE内嵌入脚本解释器后,需要自己调用这个函数。而且从该函数可以看出,Lua的就需要一个lua_State指针,说明Lua是能支持进程内嵌入多个解释器的,而Ruby和Python都是没有参数的,说明一般说来只提倡一个进程内嵌入一个解释器。  曾经看到有人(似乎是SWIG的作者吧)说,SWIG生成的代码可读性很差,今天我看了看,觉得还可以,需要关心的部分还是能大概猜出一点意思的,而其他的都是辅助性的代码,也就是完成实际的接口转换工作的。除了这个问题,还看到网上有人说过,SWIG实现的粘合层,在效率上会不如直接手工写的那种,今天我看这些生成的代码,觉得此种言论是立不住脚的,因为完成实际接口转换工作的代码都是固定的,应该也是SWIG的开发人员先手工写好的,在效率问题上并不是值得纠缠的。  用SWIG扩展exe内嵌入的脚本解释器,可以大大减少工作量,降低出错的机率,实在是一种值得推广的工具。比如最近在考虑的,在C++程序中实现一套仿Eclipse的插件机制,使用外部脚本实现程序的业务逻辑,这时用SWIG就可以直接将大量内部核心接口转换成内嵌的脚本解释器可接受的形式,极大地提高工作效率,对于使用COM接口的方案,真的是不太感冒!

去见疯丫头

  中午离12点还差10分的时候,突然接到疯丫头的电话,叫我拿100块钱去给她。翻出钱包才发现,空空如也,急忙问旁边的同事借了100块钱,又急忙坐电梯到1楼,还以为这样会比走楼梯快一点,再急忙坐穿梭巴士,巴士开得慢悠悠的,我急不可耐地让司机可不可以开快点,不得地看时间,好不容易在6分钟内赶到A10,进了大门看到一着正装的MM站那儿,本还打算上去问一下她101房在哪里,走近一看才发现,这正装MM居然就是疯丫头,哈哈,她也会有穿得这么正式的时候啊!  差不多有一个月没见过她了吧,跟着她领完东西,刚好就是吃饭时间了,于是去A8吃饭。路上偶然发现,她左边下巴上的那些东西还没抹散,呵呵,这个丫头这一上午走来走去,不知让多少人偷偷笑过了。  A8的饭菜感觉就是比F2的要好吃,我还是打了两个素菜。一边吃,她还一边给我讲她新岗位上要做的事,现在在客工部实习,下午就要去接待客户。像以前一样,我很快吃完了,看着她吃,互相讲些自己最近的情况。  吃完饭,慢慢走回来,她就回G1去了。她还说,如果干几个月,觉得不好干,就辞职。我猜,可能还有其他原因吧。唉,人就这样一个一个都走了,我的心就是这样一点一点被敲碎的。

打算用WTL写个围棋打谱程序

  写个围棋打谱程序,这个想法好多好多年前就有了,上高中的时候就有了。大概是因为当时对围棋有点点感兴趣,纯粹的叶公好龙型的,虽说感兴趣,却没有认真学过,只是知道大致的规则而已。这些年,看StoneBase的发展,觉得挺有趣的,不过它的实现不是我喜欢的方式,所以我突然又决定自己写一个,而且想了想打算用WTL来写。  开始是有点犹豫用MFC还是WTL的,因为MFC相对较熟悉一点,而且有Xtreme Toolkit Pro可以用,做炫酷的界面确实方便。但是后来想想用WTL就看中它生成的可执行文件体积小巧的优点,看看StoneBase当前最新的4.6.1版本,exe文件也就只有8.24MB,而假如用XTP的话,当是它的dll就有5.5MB,还要加上MFC的dll,大概是3.6MB,这样附属的文件体积就已经超过StoneBase主程序文件大小了。还有一个想法是,希望能借此机会好好学习一下WTL的使用。  至于要做得什么样的,从特性外部行为上看,可以模仿StoneBase和MultiGo。比如首先要能支持几种国内常见的棋谱文件格式,还要支持棋谱库,MultiGo没有,StoneBase 用了一个叫Absolute Database的嵌入式数据库,我猜大概因为StoneBase是用Delphi开发的缘故吧。我可以用SQLite来替代的,不过StoneBase有一个很庞大的棋谱库,所以也需要能以某种方式读取它的数据库。需要有良好的打印支持功能,肯定很多情况下,需要把棋谱打印出来,对着纸自己敲棋子,那种感观享受不是电脑程序能比的。要有方便的棋谱输入编辑功能,要有格式转换功能等等。  具体实现上,我想尝试一下纯插件框架,也即用C++实现一组核心的功能,其他高层的业务逻辑全都使用外部脚本扩展实现。这种框架有点像Eclipse,又有点像Mozilla的XUL方案,要扩展的地方包括主菜单、工具栏、弹出式右键菜单、棋谱格式读写等。

有些问题是看不出来的

  有些问题是看不出来的。  今天因为有人提了单,我才下决心去看了一下代码,经过比较仔细的排查,最后结论是,果然是被我改坏了,原来的那种实现方式确实是不会出这种问题的,不过那种实现方式是一定不能留的,一定要被改掉的,只能在现在的基础上进行修改,最后好像勉强可以了,哈哈。  还有一个问题,好久好久了,是以前离职的一个人留下来的东西,交给其他部门人用的工具,现在发现有问题了,原来我看了一下,似乎问题不在工具本身,而在工具调用的底层通信模块上。今天又被人一说,仔细读了一下工具的源代码,再加上和那底层通信模块作者的讨论,最后结果是,确实好像是工具实现得不对。有几行代码一眼看就觉得写得有问题,且不说是否真有问题,首先代码就不应该是那样写的。  另外是这大半年来我陷入其中的工程,今天写用户手册,当然需要演示截图,结果发现了好几个比较严重的问题。唉,我这大半年的投入产出比还真是低,心里有点难过。争取在剩下的3个星期里,把这些严重问题修正了,完善一下用户手册,就彻底交付了,唉,从开始计划的2个月,到现在都大半年了,真是没完没了,还拖累了绩效,不过似乎就算不搞这个,绩效也不会好到哪里去吧,郁闷!  最后提件还算好的事吧,小思宇回深圳了,今天还给我打电话,挺高兴地跟我说“我回来了”,大概是因为彭彭回来了吧。不过她那高兴的劲也短暂地感染了我,让我如沐春风啊,哈哈。

自动升级程序

  今天一时兴起,修改了一会儿自动升级程序。这个自动升级程序半年前就做了,当时用的boost::asio来实现http下载文件,可是问题就在于下载时,CPU占用99%,而且下载速度并不快,这让我很郁闷,在网上也没有找到确实可行的解决方法。后来觉得功能基本可用了,就一直丢在那里不管了。  前些日子,另一个同事也开始为Impeller做了一个自动升级程序。那个程序的实现我不喜欢,它在升级时没有一个可见的需要升级的文件列表,进度条也只有一个总的,总之至少从界面上看,是个很土的程序。不过它除了能升级本地应用程序的必要文件外,还能进行Gems包的升级。另外,似乎它还可以在文件被替换的前后,执行一些额外的操作,比如一个COM组件可能需要注册,这时这个功能就很有价值了。不过,有时候我又觉得,有必要那么复杂吗,它还想内嵌一个Ruby解释器,能执行Ruby脚本,晕死!  在我心中,一个比较理想的升级程序应该是这样的:1、每次都是从一个指定的地方获取一个文件列表,文件列表中记录了可升级的文件的相关信息,如文件名、相对路径、大小、hash值、日期、版本号等,每次都是将本地的文件跟文件列表中的信息进行比较,才得出是否需要升级的决定;2、一般下载文件都是通过http的方式(比较简单)实现,这样可以实现多线程及断点续传;3、下载时,有针对当前下载文件的进度以及所有文件的总进度;4、允许用户中断升级过程;5、能自动替换文件,包括被打开的文件;6、能最小化到托盘图标;7、能通过配置就可以直接适应其他升级需求(配置可以放在文件中、注册表中,或直接通过命令行得到);8、在替换文件前后,可以执行简单的命令行,不需要执行什么脚本程序那么复杂;9、升级程序自身文件体积应该比较小;10、有良好的出错保护机制;11、可自动生成第一步需要的文件列表;12、支持子目录中的文件升级。  暂时就只想到这些,呵呵,抽空用WTL写一个。

继续读《重构》

  《重构》中文版一直都放在公司电脑前,随时读上几页。有时不觉会为其中的精妙之处深深折服,大呼过瘾。看着一种一种的重构手法,该手法的使用场景,不时会有深有同感的感叹。  有些情况下,自己写的代码,写着写着,自己都觉得很不爽,但不知道问题出在哪里,功能是可以实现,但总会觉得不舒服,又不知道如何改进。在读《重构》时,就很可能能遇到甚至一模一样的所谓bad smell。  今天偶然跟同事谈起,我已经不再喜欢看跟语言相关的书了,同事就略带玩笑的语气说,这是架构师才会做的事。确实,我已经不再满足于那种与特定语言相关的奇技淫巧了,我更关心的是如何写出更少bug,更多弹性,更易理解,更好维护的代码。  但是在读这些书的过程中,我还深深地体会到熟练掌握并运行设计模式的重要性。现在的我,对设计模式几乎一无所知,能产出的代码也是最简单的过程化结构,往往能在最早期的时候快速写出一些代码,但后续的维护和扩展却越来越困难。从读《重构》一书中,体会到,也许只有设计模式,才能解决我目前所遇到的这些问题。不然,即使能看出问题所在,但不能运用正确的解决方法,依然是毫无用处。

理论考pass了

  今天去理论考了,考前还是有点担心的,因为感觉准备不充分,本来打算上周日好好看看书的,结果经不住诱惑去看小说了,还看得天昏地暗的。昨天晚上看完一遍,今天早起又开始从头开始看,在公司里也看了一上午,一直到11点半,才跟江江一起出去。先去四季花城,吃了顿快餐,凉拌的牛肉做菜,味道还不错。吃完看时间还早,便又在花城里面的椅子上坐着看书,有点大学里的感觉,平时不努力学习,到考试前夕就着急抱佛脚。  然后去花城门口等,等了一会儿接送车来了。在上面随着车的颠簸,有微微开始有点困意,确实本来这几天就因为看小说而睡得不多,加上一直来这个时候就是午睡时间,困是应该的。看江江也是眼睛一闭一闭的,呵呵。  一觉醒来,发现已经到车管所了,醒得还真是时候。上楼,签名,验指纹,排队等候。这是一个exam pool,一个人考完出来,交了座位牌,外面等的一个人才能拿着座位牌进考场。江江进去没几分钟,我也进去了,不过我不知道江江坐哪里。我拿着1号的牌,略带紧张。  每个座位左上方都有一个监控摄像头,座位之间都有挡板分隔。没有键盘,只有一个显示器和一个鼠标。我飞快地答完题,虽然还是有几题拿不准,不过我猜应该能pass吧,1分1题,最多允许错10题呢,拿不准的大概也是10题左右,再怎么也是有蒙对的吧。结果第1次交卷还太早,一定要开考后15分钟才能交,于是又把选择题看了一遍,再提交,有点紧张,94分,万幸,呵呵。  拿了成绩单,走下楼,江江还没出来,于是等了一会儿。江江出来时看到我还有点奇怪,说没听到我的名字,呵呵,她考了96,果然比我好呀!  到接送车的地方,开车的司机说要等到3点半,看看时间还2点半都不到,于是我和江江决定去外面自己找车走。刚走出,就遇到一帅哥问我们是否一路,于是又找了一美女,4人在路口等了好一会儿才拦到一辆红的,真的是,太少的士了。一直到公司,我拿了35块钱出来。  爽啊,就等上车了。

对那东西很失望

  今天考评,结果还是不出我所料,有点麻木地失望。不过这失望不是本篇blog的主题,这里要谈的是目前项目组里的重头项目,那个已经历了超过一年半开发的IDE。  这东西说要重构,我也已经开了个头,现在的情况是,老大说要在VSS上建分支。可是很明显的是,VSS根本就不支持这样的开发方式。这样就会引入一种新的问题,模拟分支,就有两份明显独立的拷贝,如果进行同步。修正问题在原有代码的基础上进行,重构则在新的一份拷贝上进行,以后合并代码对于现在的几个人来说,估计是个挑战,但我猜更多可能的直接是个灾难。  其次是架构和代码实现。现有的架构方式已经寸步难行了,但还是要在那个基础上继续进行。现在我是切实地体会到,编码产量高的,一点都不稀奇,相对来说架构设计则真的重要得多。一个文件,一个类实现,用C++代码实现,居然有18000行,这样的实现,功能确实做出来了,但后续的工作却举步维艰。一点小的修改,一点小特性的增加,都会牵扯到很多地方,可能引发很多问题。模块间的交互耦合,都没有经过良好的设计,都是觉得怎么容易实现,就怎么写,结果一堆一堆的重复代码,一个个超大的文件超大的类。  再次是加入特性没有经过认真评估,加入了一些完全不实用,花里胡哨的东西,而真正有用的功能并没有加入或增强、完善。比如about对话框,居然是一个视频文件,直接调用MCI接口播放实现。视频的周围则是一张背景图,背景图上的文字都是在图形处理程序中固定写死的,这样在以后每次显示信息修改的情况下,凭空增加了图片维护的工作量了。另一个是状态栏上的动态新闻,嵌入IE直接显示远程服务器上的内容,程序内部截获打开链接操作。大概是实现方式有问题,如果远程服务器宕掉了,则在程序启动时试图连接时,花费好几秒时间在那里阻塞等待。诸多问题,恕不一一列举。  最后是没有好好利用持续集成。虽然装了个CruiseControl,却只是为了实现点个按钮进行编译,连“持续编译”都算不上。一方面,没有实现“持续”,都是在需要时,手动点击一个按钮,进行force building;另一方面,只有编译,没有其他行为,比如代码度量、单元测试、打包发布等等,完全是个空架子。  其实回头看看这些问题,我觉得都是管理上的问题,单纯从管理层的角度出发,就能解决绝大多数。  总之,我对那东西很失望,因为管理者如果没有意识到问题根源,将在以后的很长很长一段时间一直存在,并继续恶化。而我能做的,大概就是假如有机会,把编辑器部分做完善点,在我能顾及到的范围内,实现一个类似Eclipse的扩展机制,仅此而已。因为今天听说,有人对编辑器评价很高,有人甚至就因为这个功能,就想把这东西拿到家里去用,这是我唯一的动力了。

我也TDD了

  TDD,即所谓的测试驱动开发,哈哈,我也开始用了。项目负责人花了两天时间总算建起一个可以正常跑起来的CppUnit工程,经过另外一个同事的验证,确实也能加入测试用例来跑。于是我兴高采烈地加入几个正儿八经的测试用例,结果发现用例是跑起来了没错,可是当从VC中想直接check in源代码到VSS中去时,VC就不干了,说两个工程不在同一个起始目录下。因为正式的功能代码是直接从原来的工程那里引用过来的,而偏偏测试工程是放在平行的目录下,因此有了这样的问题。万般无奈之下,只好亲自动手,把测试工程也移入正式工程的目录下,修改一下路径包含,一切OK,check in也没问题了。  几乎所有看得到的讲到TDD的书、资料上都说,在写功能代码前,要先写好测试用例。但是我发现直接这样做还是比较困难的,我现在的做法基本上是先想好,会有哪些功能类,然后就写好对应的测试类,再写好功能类中的成员函数声明,并用Visual Assist X生成一个空的函数实现,再跑到测试类中添加对应的测试函数,这样测试工程应该还是能跑起来。接着,我基本没按书上说的那样按部就班先写好测试函数内容再写功能函数内容,而是两边来回切换,一边写一点切换到另外一边再写一点,来回倒几次后,也基本都完成了。我不知道我这样做有没有什么不好的地方,反正用CppUnit这样做,进行单元测试确实方便,对于一些算法性的函数实现,直接在功能实现工程中往往不好测试,而放在CppUnit的测试工程里,则是再合适不过了。比如今天从别处抠来一段用WinINet进行Http下载的代码,略作修改,放在测试工程里测试,太方便了。  另外还有点体会是,设计模式是一定要学的,但总觉得GoF的《设计模式》看得太乏味,太枯燥,太艰苦了。Bob大叔的《敏捷软件开发》,Martin Fowler的《重构》都是要认真看的,还有《重构与模式》、《修改代码的艺术》、《反模式》都要好好看一下。提升个人能力,这些是必经之路。