在Kugoo上随便按排行榜搜索的歌曲列表,几乎是重装一次才会更新一次的列表。这次偶然发现列表中一首很抒情的歌曲《白狐》。虽然听了也好些日子,而且都是一个人在静谧的夜里听,但直到今天,我才实在忍不住心中的好奇,上网搜索了一把。
搜索了才知道,原来还有一个合唱版本的,于是马上开Kugoo来听,男声稍微有点让我失望,也许是从小看电视《聊斋》而被先入为主的思想主导,我特别希望男声是像电视中的那种书生腔调。不过总的说来词、曲,以及女声,都很打动人,在百度百科中,也有不少相关的信息,原来蒲松龄不是第一个写书生和狐独的故事的,只不过是他把如此凄美而又有新意的情爱带到了大众面前。
不知怎的,尽管让人觉得悲伤,我却有点神往。
真的。
现在一个Lua解释器的封装类,一个插件注册表类,这样的结构是不够用的。至少需要有一个管理插件运行的模块,它可以屏蔽掉解释器的底层差异,使得使用这个模块的用户不用知道他们到底是在运行Lua脚本还是其他Ruby或是Python之类的东西。另外一点是这模块需要向不单是 C++代码提供接口。也就是说,用脚本写成的插件需要使用这个接口。这个需求很合理,也很必要,不然的话,很明显的一大限制是不能再对插件进行扩展了。
因为怀疑,所以去确认了一把。自己也懒得编译了,直接从网上找了个人家编译好的wxLua,人家是用VC8编译的,我的工程是用MinGW编译的,结果真的可以加载了,我狂晕!不管了,大不了到时候提供个Lua for Windows的安装包链接。
再回来说插件的问题。昨天主菜单是能运行起来了,不过发现有个不爽的地方。因为插件是放在一个指定目录下的,搜索出来的顺序可不是人能控制的,于是添加到菜单上的顺序也是乱的,这就不好了。由此引出另一个需求,对于一个插件应该可以定义多个菜单项,这样至少可以让这一个插件中的菜单项保持可控的顺序。
弄了一整天,插件框架基本上运行起来了,至少主菜单部分能出来了,证明这套机制是没什么大问题了,剩下的没解决的都属性主菜单的问题,而不是插件框架的。
不过之后郁闷地发现,用MinGW编译的使用了wxWidgets的工程,不能使用wxLua,无论是通过C API加载,还是通过Lua代码加载,都不成功。用VC编译的工程就不存在这个问题,如果是普通的工程,没用wxWidgets的工程,也是可以加载的。于是我猜测是不是因为加载相同的wxWidgets而冲突了啊!因为wxLua是用MinGW编译的,还没试过用VC编译来wxLua来试试。开始还怀疑是不是因为路径的问题,或者是用了Luabind的问题,或者是用了SWIG的问题,后来实验发现好像都不是。
用VC编译了wxLua来试试吧,如果还是不行,就没办法了,只好不在扩展脚本中使用wxLua了,唉!本来还以为用wxWidgets的工程用wxLua是绝妙的搭配呢,大不了再试试IUP之类的呗。
花了近半天时间,把about对话框弄完了,不过有点小问题,不能设置tab页的title。整天琐事很多,最近把崩溃问题解决方案稍微整理了一下,说实在的,我心里是没底的,这个任务对我来说似乎要求高了些。我也希望程序能7×24小时地运行不中断,可是无论怎么弄,稍有点复杂的功能肯定就埋藏了大量的bug。
今天发现原来我的机器上一直跑得好好的程序,到其他机器上就不行了。于是另外找了个装了Win2003和MS Office2003的机器上调试,发现首先我用了一个Excel2003中不存在的接口,Excel2007中是有的。于是绕了个圈子也勉强达到原来的目的了。
接着发现把图表copy到系统剪切板中再取出来存成图片的文件不行了,根本枚举不到GIF类型的剪切数据。在网上找了个剪切板观察工具,发现确实没有那个数据,不是程序的问题,原来Excel2003的接口功能就是要比Excel2007弱。于是绕了好远的路,换个接口,把图表当成图片copy到剪切板中,发现剪切板中有Meta file picture类型的数据,再用Win32 API把这数据保存成文件。很扯的是这文件不知道是不是需要什么句柄释放操作,反正直接是不能修改或删除的,不过还好能复制。接着用GDI+把这个wmf 文件转换成jpg或gif。结果郁闷地发现,转换后的图表背景变成全黑的了,想了好多办法,转来转去都是黑的,只有wmf和png看起来是白的。其实大概是透明的,整了好久都没办法,下班的时候跟老雷说起这个事情,老雷说先设置一下背景色。我真想拍一下自己的脑袋,怎么就没想到这点呢!
明天再去弄了。
老雷说,画图功能可以让Excel来做,我曾经还抱着试试看的心理在网上找有没有免费的代码可以方便地生成各种柱状图、饼图的,没找到,也实现懒得自己去画了,于是硬着头皮去用Excel了。
VC的程序操作Excel,而且不光是为了存取数据,所以用COM是最正规最有效最理想的方法。如果是用MFC,可以让IDE自动生成其中的某几个接口的封装类,这样的好处是封装得比较高层,使用起来更方便简单一点。如果直接导入类型库,会生成整个类型库内所有的接口和类型的封装,不过是底层一点,最多是用一下COM智能指针封装的接口,可以少处理一些引用计数的问题。
先可以录一个宏,查看VBA代码,就基本知道要用哪些接口的哪些方法了,但实际上如果用VC通过COM来操作,会发现跟VBA的结果经常有些出入。这让我有点纳闷。不过今天整了一天,总算勉强完成任务了。
想起插件的事,如果一个软件具备插件扩展能力,同时又希望其用户自行开发插件,那么这个软件一般说来需要提供一个良好的插件开发环境。纵观 Eclipse,MSVS、MS Office等都是这样的,而且VS和Office中的扩展形式除了插件,还有宏,准确地说来,是开发宏的环境是随软件一起提供的,而插件的开发是要用其他工具的,比如VS。而我前面说的插件开发环境,其实对应的是宏开发环境。
这个开发环境简单的做法,应该是一个独立的程序,但与软件主程序之间有很深入的进程间通信,不然调试功能是不可能那么完善的。也有可能是还有一个中间的宏解释执行器,主程序和宏开发环境都是只与宏解释执行器进行通信,同时该解释执行器也有调试功能。这样做的一个明显的理由是,当调试宏时,主程序可能会被挂起,而这时调试界面则要求是活动的,所以放在主程序中是不容易处理的。
Eclipse可能不需要这么做,它也许可以在不同的实例中使用不同的配置,这样可以在一个实例中调试另一个实例。
还有一种比较极端或者不负责任的做法是,不提供开发环境,让插件开发者痛苦去吧。也许用比较高级的脚本语言来做为扩展架构的底层执行器,这个问题的影响会变小,因为脚本语言对调试的需求可能会小于像C++这种编译型语言。但我觉得无论怎么样,提供一点基本的用于调试的设施还是必要的,比如至少可以加入一种打印输出机制之类的。
今天在公司里看到google test和google mock,又好奇心起,看了看,觉得比起CppUnit来真的简洁不少,而且CppUnit没有一个好用的用于插桩的框架,那Mockpp和 Mockcpp都有很明显的很影响使用的这样那样的限制和缺点,而google提供的这两个框架则比较好地解决了这些问题。
回到家又上网下载 gtest试试,它有msvc的解决方案文件,也有GNU make的makefile。不过msvc倒是能直接编译通过,而MinGW则是不行的,要改些地方。首先要将MINGW的识别宏添加到 GTEST_OS_WINDOWS宏定义中,然后在几处使用了SEH的地方,__try/__except都是VC才用的东西的,只要再在编译开关处把 MINGW去掉。这样gtest库是可以编译通过了,不过有一个自测试文件gtest_unitest.cc却还是有问题,要加个文件头包含 limits.h,这样就可以全部编译通过了。
又顺带看了一下googlemock,它依赖于tr1。在公司里,我什么都没修改,直接用 VS2008编译过了,在家里却不行,总是报VC的xtr1common文件中的什么tr1没开启。上网找了下解决方法,加入boost就行了,不过我试了发现从trunk中取到的boost不知为什么不行,最后还是从googlemock的官方网站上下载到一个他们从boost 1.36.0中提取出来的tr1中tuple部分,这样在编译包含路径中添加boost和tr1的路径,就可以编译过了。但它在源代码中明确限制了只能使用VS2005或更高版本来编译,看来要用在MinGW中是有点困难了。
今天把一个用了wxWidgets、Xerces-C++、luabind的工程用bjam写了个构建脚本,可以用MinGW来编译,也可以用VC来编译,这就是bjam的好处,很少需要关心不同编译器的命令行参数。
经过这次实践,还发现alternative的选择原来是要跟它的属性完全对应上才行。比如一个目标,写了toolset和variant以及 threading,则在命令行中要把这三个全都设置上,不然它就说找不到匹配的目标。这让我有点头痛,假如我只想在命令行上选择toolset,而让其他两个属性固定死,那怎么办。
不过还算可以了,目前基本能满足实际需求了。
bjam编译Windows下的rc文件不用特别指定,就像其他C/C++代码文件一样,直接放个文件名就行了,它能自动认出来,并调用相应的资源编译器来,只不过生成的二进制文件后缀还是跟代码文件编译出来的一样,不过并不影响最后的链接过程。
对于debug或release下使用不同配置的情况,需要至少每一种情况写一遍目标,同样如果是不同的toolset也这么处理。
今天把一个使用WTL写的工程用bjam系统编译了一把,果然也不是很麻烦,不过需要写不少特定的参数,bjam的默认配置不够用了,所以这样的情况还是不适合用bjam的。简单地说来,适合用bjam的场合,至少是没有一个好用的IDE的情况下。
想起直接在bjam里用wx-config来生成的编译选项,居然会出错,因为wx-config的输出后面多了个回车,用make就没影响,而链接选项在bjam中也没影响。没办法,去网上找到wx-config的源代码,只有一个文件,不过有几千行,稍微找了一下输出的代码,把末尾的回车换行去掉,再试了下,果然可以了。
要在工程的根目录下放一个Jamroot文件,可以没有扩展名,也可以加.jam为扩展名,这样其他子目录下的可以命名为Jamfile了。
基本上是个大小写敏感的系统。
还没找到怎么让它来自己编译Windows的资源文件。
用path.glob-tree可以指定目录来进行glob-tree,如果光用glob-tree,是只能从当前Jamfile所在目录开始全部搜索的。
在编译Luabind成独立的库时,顺便学了一下bjam。bjam在某些场合下挺有用的,如果之前用的是make,再来用bjam,感觉就是用过汇编之后来用高级脚本语言。
折腾了一阵子后,我的感觉就是bjam几乎是完全给boost设计的。其中最突出的特点是,编译器无关。它在这方面做不了少工作,只要指定一个 toolset,它就能自动查找编译器所在的路径,而且不用关心编译命令行,因为大部分常用的选项它都提供自己的抽象方式来表达。还有就是它似乎有良好的可扩展性,在boost的源代码目录下有几bjam的工作目录,下面有几个子目录,里面分别存放着一些jam文件,在写jamfile时,可以 import模块进来用。
我捣鼓了一阵,把自己一个工程,里面有几十个C++源文件,从GNU make转成了bjam工程。感觉有点新鲜,但总的说来,它应该是适用于这样的场合:工程是可移植的,即要跨操作系统,跨编译器。
写着写着,发现Luabind不是先前想的那样有了SWIG就可以抛弃了的,还是有用武之地的。
主要体现在两个地方。
一、从 C++调用Lua函数。Luabind提供了两个方法,在Boost的协助下极其出色地分别实现了调用全局函数和其他可被作为函数调用的对象的调用。这两个方法都以C++模板实现了可变参数类型和可变参数个数的调用,而不是C库中printf使用的那种方法。这得益于强大的Booster::mpl。
二、提供C++中自定义类型的高度灵活的部分封装。用SWIG通过分析C++声明一股脑儿全都封装了,当然可以通过预定义宏SWIG可标识不想被封装的部分,但这需要修改C++源代码,以致于破坏C++源代码的整体风格,而且灵活性明显不行。用Luabind可以随心所欲地把任意一个类的其中几个方法封装了,这个优点是我在看了Luabind中自带的几个example才想到的,比如它演示封装了 boost::regex,boost::filesystem,如果想用SWIG,几乎不可能达到目的。
主要的内容生成功能基本完成90%了,于是就研究一下怎么把这html内容通过SMTP发送出去。原来有一个从CodeProject上找的SMTP 类,直接用WinSock写的,倒是也可以正常工作,接口也很简单,而且当时沾沾自喜经过自己修改,是可以支持中文的,其实是自己土了。现在只是想把这个类再修改一下,可以直接发送html内容,不过看起来似乎有点儿复杂。
用Outlook Express发了几个邮件,用Wireshark抓来看了看,看得云里雾里。后来找到另外一个示例程序,有源代码,也可以发送html,用 Wireshark看了看,发现差别还真大。最后看到一个说明里提到,RFC2110里就是讲这个的,于是马上找来瞄了几眼,终于有点儿理解了。原来 Outlook Express在一封邮件里包含了2封邮件的内容,分别是html和plain text的,这样无论对方的客户端能不能支持html阅读,都可以方便地看到真正的邮件内容,当然html中的链接嵌入对象除外。
回来整了几个小时,没搞出什么花样,只是把tab页对象换了个地方。本来是没想到要换的,只是今天突然想起,要截获tab页关闭的消息,于是折腾了那么久,发现在原来的那个类里搞不定,不知道是什么原因。开始我只是想试试直接用wxFlatNotebook对象的Connect方法,可是根本不知道那些参数要怎么写。后来我把那类从wxEvtHandler继承了啊,消息映射也加了,但就是不行。后来看到wxFlatNotebook官方的 sample里是把这消息映射放在一个wxFrame里处理的,于是我也照样学样把它放到MainFrame里去了,果然能截获了,而且从设计角度讲,这也比之前那样好,因为这个wxFlatNotebook应该是全局使用的。只是没弄懂原因,有点不爽。
老雷要一个自动发汇报邮件的程序,这个任务落到我的头上,于是我这周开始整这玩意儿。所有的数据几乎都是存放在MySQL里的,一个是RedMine 的内容,另一个是组里自己开发应用程序使用情况统计工具,服务器端也使用的MySQL。今天我发感叹地跟开发那应用程序使用情况统计工具的同事说,幸亏你们当时选用了MySQL,不然还要更麻烦一点,比如万一选了个Access或者Sqlite3,还不能方面地远程访问。如果是SQL Server,还得使用一套不同的数据库访问方法。
通过这两天使用mysql++来访问MySQL的经历,我感觉无论是MySQL引擎,还是暴露的API,都挺不错的。大概是因为ADO访问SQL Server用得有点反胃了。那段经历让我觉得最为不爽的是,如果SQL写得有点问题,就会抛异常,而且这异常就是catch不了,因此很难排错。这次用 mysql++在这方面就做得不错,异常可能catch到,而且给出的出错信息很准确,定位很方便。另外有点值得一提的是,mysql++里有一个重载了很多类型转换符的String类,呵呵,这也是一种办法啊,不过使用operator+时却遇到了点小麻烦。
开发这个程序最大的收获大概就是这个了,大部分情况下,可以用MySQL来代替MS SQL Server了。再扯远点,Access也有点不好的,打开的时候特别慢,用Sqlite就挺快的,而且Sqlite3还是是弱类型的,呵呵。
无论是软件,还是程序库,应该选定一个并不太旧的稳定版本,一段时间内,比如半年,一直使用该版本。
一个明显的例子是之前用4.3.2版的gcc编译好的wxMSW,后来年到4.3.3版的gcc了,就下载下来试用了一下,发现原来的工程死活链接有问题,报什么虚函数找不到云云,再换回 4.3.2就又可以了。由此看来,4.3.2和4.3.3编译出来的.o文件可能在内存等方面有变化。我也不想再花时间去用4.3.3编译一遍wxMSW 了,太浪费时间了,而且如果要换,就要把其他相关的库都换一遍,至少包括boost,Xerces-C++,wxLua,青春啊,不能就这么耗费掉啊!
首先,这个架构是一定程度上模仿Eclipse的,并做了大量简化,所以模仿得并不像,不过不得不说最原始的灵感和很多想法是从它那里来的。
支撑起整个可扩展架构是一个类,大概名叫pluginsRegistry的类。这个类一般可以使用singleton模式,它做一些与插件相关的最基础最底层的事。比如,在宿主程序(这里一般是一个C++程序)启动时,它负责扫描指定的一个目录(包括子目录)下所有文件,找出其中的plugin.xml 文件,也就是插件描述文件。它并不一定要是xml来描述,也可以是用Lua来描述,毕竟Lua最早的用途就是作为配置文件的,而且这个文件并不会被程序动态修改,Lua也够用,但要注意的是名字空间污染的问题,一个办法是每个描述文件都使用完全相同的结构和变量命名,但每次都单独启动一个Lua解释器来解析。一般说来一个描述文件中声明一个插件,也就是一些基本的插件信息,包括插件id、插件扩展点和插件对应脚本文件路径这三项最主要的不可或缺的信息,以及一些诸如被扩展点,版本号,依赖关系,版权信息等等不那么重要的信息。扫描到一个插件后,这个类会把插件id,脚本文件路径对应着保存起来,并把该插件添加到指定的被扩展点上。所谓扩展点,其实准确一点说,应该是被扩展点代表一个插件会被激活(其中一部分)的时机。被扩展点一般也是由其他插件提供的,也就是说其他插件会在某个它认为合适的时刻,激活所有注册到被扩展点下的所有插件。所以插件描述文件中很重要的一点是,需要声明自己是扩展了哪个被扩展点,不然就永远不可能被激活。由此可以看出,另外一个需要注意的是,一个插件一般会依赖于另外一些插件,至少是依赖于提供它要扩展的被扩展点的插件。一个插件必须要指明它要扩展的被扩展点,这是别的插件提供的,另外它也可以提供自己的被扩展点,让另外的插件来扩展自己的功能,当然这不是必须的。为了说起来不那么拗口,在描述文件中,一般我称前者为扩展点,称后者为被扩展点,这是把同一类事物在一个所属中以其不同的功能或角色来区别而得来的。再回到这个类的功能上来,除了前面讲的这几点功能外,它还应该能向外界提供一个服务,通过该服务外界可以根据指定的被扩展点查询到所有注册在该被扩展点下的插件,理论是希望只是通过一个id就能唯一标识一个插件。这样每当时机合适时,提供被扩展点的插件就可以通过这个服务来做一些事情,最常见的是通知所有该被扩展点下的插件即可,那些插件自己应该清楚这时应该做些什么事情,还有种情况是,提供被扩展点的插件应该可以从中区分出一些插件,而不是全部,来通知。一个很常见的应用场景,主菜单,可以是一个菜单项对应一个插件,这时每当用户点击了菜单项,那么只要激活该菜单项对应的那个插件即可,而不是所有主菜单下所有的插件都要激活一遍。
通过前面的阐述,可以知道,这个类需要一个清晰简单的接口,这样才可以比较方便地既给C++代码提供服务,又给Lua脚本提供服务。尽管Lua从设计上就考虑了很多跟其他语言交互的情形,但这里我还是得小心一点,少使用一些只有某种语言特有的一些元素或特性。
最后,以一个实例来简单叙述一下这个架构是怎么真正让一些功能运作起来的。就还是以主菜单为例,主菜单也是一个插件,当然,它是用C++还是用Lua实现的我们不关心。重要的是主菜单插件首先有一个自己的id,并提供了一个被扩展点,假设叫view.ui.menu,这时假设那个类已经把所有扫描到的插件正确地分析过了,这个被扩展点下已经有一些插件了,比如file_open,file_save等等。主菜单插件在自己被初始化时,调用那个类来得到所有view.ui.menu下的插件,并依次激活这些插件的get_caption方法,根据这些个方法返回的内容,作为菜单项的标题,逐个添加菜单项,并把菜单项的id和它对应的插件(可能就是用插件id来表示)保存起来,当用户点击一个菜单项时,主菜单插件可以得知用户点击的菜单项的id,并从之前保存的信息中得到它对应的插件id,以此来再调用那个类来激活唯一对应的那个插件,那个插件知道用户是点击了菜单点,就执行一些操作,比如打开文件等等。
大致的流程就是这样。
昨天晚上6点半的飞机,回到深圳住处已经10点了。在西安过了6晚,有4晚出去转了转,给我印象比较深刻的是那里的吃的和人文景观。
22 号傍晚到的西安,结果最郁闷的是被人忽悠了,居然有两个名字一样的酒店,而且我明明说得很清楚是哪条路上的,结果司机还是把我带到了另一个酒店那里,太扯了,比国产007还要傻冒。马上出来打车走,结果说明了是东仪路,那个司机硬是开到了东一路,我大汗!好不容易啊,才到了最终目的地。一个小小的窄窄的地方。放下东西,去酒店附近一个砂锅店吃了些东西。第一天,给我最大的感受是,西安美女挺多的!在的士上时,就看着路上的行人中,很多漂漂的mm,后来去砂锅店,就又看到一个很漂漂的mm一个人在那里很优雅地吃着粉丝。
23号晚上,听从王同学的建议,去大雁塔喷泉广场转了转。到了那里才郁闷地发现,这明显是个情侣才来的地方啊。可怜我一个人,天又冷,好凄凉!随便走到路边的一个店里,买了点东西,回去哄一下家里的大小美女。其中一条街上,一家一家的小店,叫百工坊,都是当地特有的手工艺作品,主要特色是可以让游客DIY,但是我看到基本没有什么人进去,不知道是不是因为已经是晚上的缘故,或者是天气冷,游客不多的缘故。正当我无所事事准备回酒店的时候,广场广播突然说有什么水舞表演,于是好奇地停下来等。水池旁边站满了人,中间也有很多人,我挤进一个地方,原来就是喷泉配合音乐,喷出不同的水柱,所谓水舞。不过也是第一次看到这种东西,还是有点新鲜感的。
24号晚上,我本想去买些玉的,因为蓝田就在西安附近,所以西安就有很多卖玉的,问了下王同学,她说去书院门买。结果等我打车到了书院门,那里的店铺都已经关门的了。没办法,于是只好走到对面去看城墙,反正王同学也说,这好歹也是西安一景啊,哈哈。门票40,我不清楚这种行情,直接进了里面。刚好有什么大唐灯会,于是在城墙上走了一两个小时,天还是很冷,回!
25号晚上开始下雪了,而且从公司出来打车,居然打不到,走来走去,过了一个小时才打到,汗!直奔回民街,跟司机聊起,他居然是上海人,一下就猜出我老家是哪儿的,哈哈。到钟鼓楼下了车,绕了一圈才发现那条回民街,真是条小巷子,而且人不是很多,我不知道是因为天气不好人少了,还是因为已经有点晚了人少了。走到另外一边,时代盛典那里,也有一条回民街,人更少,店都关门吗。没办法,打电话找王同学确认一下,王同学也跟我说不清楚,不知道她哪里找来另外一个mm来跟我说,我才明白,第一次进去的那条小巷子是对的。于是对跑过去,吃了笼贾三灌汤包就很饱了,晃悠晃悠晃到另一边,随便找了个泡馍店,进去点了个牛肉泡馍,不得不说,这味道真的比公司食堂的好太多了,肉嫩味美。
26号,下大雪了,早上起来发现屋顶上,车顶上都积了几厘米厚的雪,白天也是飘着鹅毛大雪。只好不出去了,老老实实呆在酒店里看小说看电视。
27号,想着第二天就要回深圳了,下了班立马又跑到回民店买些吃的带回去。顺便去吃了下之前那个mm推荐的红红炒米,感觉跟蛋炒饭差不多,不过是里面有些肉丝和酸菜。又叫了几个羊肉串,还有不知道是什么肉串,味道都不错,至少这羊肉串我是比较得出来的,比起其他地方吃过的,这里的特别嫩,而且膻味不大。吃完后,晃悠了两圈,进到一个大一点的店铺里,买果脯,就挑那种平常在深圳不太有得买的,我也不知道价格上有没有问题,不过想来再亏,数量也不大,也就不去计较了。
偶尔去下这种从没去过的地方,还是挺有意思的,不过一个人毕竟乐趣就少了些了。