All Stories

继续bjam

  bjam编译Windows下的rc文件不用特别指定,就像其他C/C++代码文件一样,直接放个文件名就行了,它能自动认出来,并调用相应的资源编译器来,只不过生成的二进制文件后缀还是跟代码文件编译出来的一样,不过并不影响最后的链接过程。  对于debug或release下使用不同配置的情况,需要至少每一种情况写一遍目标,同样如果是不同的toolset也这么处理。  今天把一个使用WTL写的工程用bjam系统编译了一把,果然也不是很麻烦,不过需要写不少特定的参数,bjam的默认配置不够用了,所以这样的情况还是不适合用bjam的。简单地说来,适合用bjam的场合,至少是没有一个好用的IDE的情况下。  想起直接在bjam里用wx-config来生成的编译选项,居然会出错,因为wx-config的输出后面多了个回车,用make就没影响,而链接选项在bjam中也没影响。没办法,去网上找到wx-config的源代码,只有一个文件,不过有几千行,稍微找了一下输出的代码,把末尾的回车换行去掉,再试了下,果然可以了。

bjam使用点滴

  要在工程的根目录下放一个Jamroot文件,可以没有扩展名,也可以加.jam为扩展名,这样其他子目录下的可以命名为Jamfile了。  基本上是个大小写敏感的系统。  还没找到怎么让它来自己编译Windows的资源文件。  用path.glob-tree可以指定目录来进行glob-tree,如果光用glob-tree,是只能从当前Jamfile所在目录开始全部搜索的。

bjam有点意思

  在编译Luabind成独立的库时,顺便学了一下bjam。bjam在某些场合下挺有用的,如果之前用的是make,再来用bjam,感觉就是用过汇编之后来用高级脚本语言。  折腾了一阵子后,我的感觉就是bjam几乎是完全给boost设计的。其中最突出的特点是,编译器无关。它在这方面做不了少工作,只要指定一个toolset,它就能自动查找编译器所在的路径,而且不用关心编译命令行,因为大部分常用的选项它都提供自己的抽象方式来表达。还有就是它似乎有良好的可扩展性,在boost的源代码目录下有几bjam的工作目录,下面有几个子目录,里面分别存放着一些jam文件,在写jamfile时,可以import模块进来用。  我捣鼓了一阵,把自己一个工程,里面有几十个C++源文件,从GNU make转成了bjam工程。感觉有点新鲜,但总的说来,它应该是适用于这样的场合:工程是可移植的,即要跨操作系统,跨编译器。

Luabind还是有必要的

  写着写着,发现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,几乎不可能达到目的。

研究了半天SMTP发送HTML

  主要的内容生成功能基本完成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还不错

  老雷要一个自动发汇报邮件的程序,这个任务落到我的头上,于是我这周开始整这玩意儿。所有的数据几乎都是存放在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,青春啊,不能就这么耗费掉啊!

基于Lua的可扩展架构设计

  首先,这个架构是一定程度上模仿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,以此来再调用那个类来激活唯一对应的那个插件,那个插件知道用户是点击了菜单点,就执行一些操作,比如打开文件等等。  大致的流程就是这样。