技术文章

Firefox插件(plugins)开发实用指南

Firefox插件可实现强大功能,但其中麻烦事情不少。写这个实用指南首先是为了方便自己记忆,免得以后再次栽倒一些坑里面,如果能帮助其他人,则是更好。这个指南不是为了手把手教读者开发插件,而是作为一个FAQ,解决各种诡异问题。
Firefox拥有众多的扩展(Extension),开发扩展也非常容易,不过有一些事情还是无法用扩展解决,需要访问操作系统的底层功能,这就需要写插件(plugins)。例如flash就是一个插件而不是扩展。
Mozilla提供了一系列的教程和文档,虽然很不详尽,众多重要的API语焉不详,但至少是一个好的开始。
最需要阅读的是plugins API和使用入门。这是一个相当长的文档,如果看完所有的内容会花费大量的时间而且还会很晕,这里列一些重点供参考。

plugins基础概念
写第一个插件(只需要关注Writing Plug-ins这一节所谈到的内容)
获得一份firefox的源码,比如firefox 3.6。plugins的例子可以在源码里找到(modules/plugin/sdk/samples),如果出了问题还可以自己编译一个debug版的firefox来调试。
了解浏览器能提供什么功能
制作插件的安装程序,推荐用扩展的方式安装插件,有无数的好处

完成以上这些内容以后差不多就已经可以实现自己的插件了,一般而言,参照着例子来做开发不会有什么问题,只是有不少细节需要留意。
Firefox plugins开发的众多奇怪的约定(假设plugins已经被正确安装)
有些约定非常奇怪,不要问我为什么,天晓得开发firefox的牛人们怎么想的。
在Windows下,plugins必须满足以下条件才能被firefox检测到:

插件的名字必须是np*.dll,也就是必须以np开头,.dll结尾
插件dll资源的语言必须为LANG_ENGLISH,code page必须为1252。在rc文件里是这么写的:

LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)

插件dll的VERSION_INFO里面必须包含以下值:
VALUE “MIMEType”, “application/x-your-mimetype”

这个MIME就是<object>标签引用插件的唯一凭证。

在Linux下,plugins必须满足以下条件才能被检测到:

插件的名字必须是lib*plugin.so,即以lib开头,plugin.so结尾
插件必须实现NP_GetMIMEDescription和NP_GetPluginVersion,并返回合适MIME字符串。注意,这个字符串并不是普通的MIME,是有特殊规则的,详见前面这个链接的内容。
插件so不要静态链接gtk、opensll、pthread、z等系统库,这会在不同linux平台上因为符号表的问题遇到各种运行时错误

特别需要说明的是,NP_GetPluginVersion、NP_GetEntryPoints等关键函数没有任何官方文档介绍它们,只能根据例子来猜,反正没事就别改它们的实现,copy例子中的代码就好。
firefox插件开发注意事项
写firefox插件的一个基本习惯是,经常编译代码并运行它,保证你的插件还能工作。只要firefox无法加载dll/so,或者加载出现任何错误,都会悄无声息的忽略这个插件。时常关注一下about:plugins,看看插件是不是还在这个列表里。
firefox插件从窗口模式上可分为windowless和windowed两种。其中,windowless模式的文档较多较全,是firefox比较推荐的模式,坑比较少,这里就不多说了。windowed模式则相反,需要好好说说。
无论在Windows还是Linux上,windowed的插件都拥有独立于浏览器页面的窗口。firefox会通过插件的NPP_SetWindow来告诉插件当前窗口的情况。
关于windowed插件有两个诡异问题需要注意:

Windows平台下,插件窗口默认会响应WM_CTLCOLOREDIT、WM_CTLCOLORLISTBOX、WM_CTLCOLORBTN、WM_CTLCOLORSTATIC消息,并设置一个默认的背景色。这本来没问题,但在Windows XP下,这个颜色居然永远是黑色,而不是默认系统背景色(通常是白色)。最好subclass这个窗口并且拦截这些消息,不要让firefox去处理它们。对于插件来说,firefox处理这些消息只是帮倒忙而已。至于firefox还帮了哪些倒忙,可以去源码widget/src/windows/nsWindows.cpp的nsWindow::ProcessMessage()去围观。
Linux平台下,NPP_SetWindow传入的NPWindow指针中虽然有一个ws_info成员,这个成员里面也确实有一个display变量指向X Window的Display结构,但绝对不要真正使用它,否则可能会导致firefox直接退出,据说这可能是firefox的一个bug。

测试firefox插件小技巧,测试方面的高手可以无视
测试插件前建议先在firefox里面创建一个新的profile(帐号)。这样可以创造一个最干净的开发环境,避免被其他扩展/插件干扰。
默认的profile名叫default,在命令行里输入firefox -p default就可以使用这个profile。如果只是输入firefox -p,会弹出一个对话框用于选择profile。这个命令在Windows和Linux下都可使用。
无论是哪个平台,调试插件的方法都很类似。
Windows下可以用VC以调试方式启动firefox,载入插件时调试器会自动载入对应的符号,捕捉发生的异常或者设断点都很方便。
Linux下直接用gdb就好,细节应该不用多说。有一点需要注意,系统默认安装的firefox命令(默认放在/usr/bin/firefox)是一个shell脚本,真正的可执行文件名字需要打开这个脚本自行查找。
实现firefox插件的基本功能
firefox为插件提供的接口十分原始,很多功能默认没有实现,下面提供了一些思路和方法。

让插件接受焦点:默认情况下,<object>标签不能获得焦点,必须指定tabindex。
在插件中使用tab键跳到下一个element:没有好办法,必须自己手动将焦点还给浏览器窗口(Linux下不必如此),然后自己用NPN_*系列函数找到应该获得focus的DOM element,然后调用这个element的focus()方法。
隐藏和显示插件:直接设置<object>标签的style.display = “none”即可,但这里有个严重的副作用,firefox会调用插件的NS_PluginShutdown,销毁这个插件。如果不期望造成这种效果,要么别用这种方式隐藏插件,要么把插件状态保存在js里,再次显示的时候把状态设回去。
触发DOM事件:firefox没有提供任何便利的方法触发DOM事件,要在插件中做到这点,必须自己模拟js触发DOM事件的过程。例如,对于HTML事件,假设self是DOM element,js会这么做。

evt = document.createEvent(“KeyboardEvent”);
evt.initKeyEvent(
“blur”, // in DOMString typeArg,
false, // in boolean canBubbleArg,
false); // in boolean cancelableArg,
self.dispatchEvent(evt);

对应的C代码就是

void FireHTMLEvent(NPP npp, [...]

架构成长之路

产生软件架构设计的动机是代码复用,当一段重复的代码在多个不同的模块之中重复多次、每个人都为这段代码写的吐血的时候,大家自然而然就想到了——我们要有一个“架构”,或者说准确的说是一个框架。
最原始框架一定是最基本的。从广义上来说,操作系统就是一个框架,它封装了每一个程序需要完成的基本任务,管理内存、磁盘IO、中断、输入输出等。不过操作系统能封装的东西有限,最关键的是,封装总是略显简单。比如,需要写一系列服务器程序,每个程序都要访问网络、保持长连接、管理连接池等,这些机制并不是几行代码就可以实现,需要一个稍微高一点层次的框架来解决这个问题。于是很自然的,层次的概念就出来了。
当框架有了层次概念之后,大家就可以各司其职,为完善各自的框架而奋斗,直到最终框架已经极为好用,每写一个程序就像冲杯奶茶一样容易。大家都很高兴。
到现在为止,软件架构似乎还非常的简单直白,但随着时间发展,复杂的需求涌现出来了,架构也就突破原来软件框架的范畴,变成更复杂的东西。
还是拿那一系列服务器程序为例,假设它们是一个在线交易平台的后台服务。某个程序员完成了个人转账业务的代码,另一个程序员完成了商户转账业务的代码,互相一比对发现,嘿,大家怎么写的大同小异,应该优化。不过这样的优化存在着风险,因为谁都不能完全确定,未来这两个业务会怎么发展。
保守的程序员会让这两份代码独立,静观其变,反正框架已经足够好,重写一份这种业务也花不了多少时间。这并没有错,不过会错失让架构突破框架限制的机会。
喜欢挑战的程序员会仔细思考这两个业务逻辑,从业务的层面来分析其中的异同以及未来变化的趋势。
比如转账这件事,无论是个人业务还是商户业务,创建交易、记账这些事情总少不了吧,这或许可以抽象出来,但是检查账户权限、验证密码这些事情肯定会不一样,这些东西保持尽可能的可变。如此这般,业务层面的封装逐渐成形,所谓的架构终于有了架构的意思。
在引入业务之前,前面提到的“架构”只是一个放之四海皆可用的程序员的玩具;引入业务之后,架构终于得以走向成熟,摆脱程序员的束缚,反过来影响程序员的思维。
真正让架构腾飞的是引入领域知识(domain knowledge)的概念。作为一个具有业务功能的系统,要维护业务的纯正性不容易。还是拿转账来说事,如果提供了创建交易和记账的业务抽象,再假设转账是一气呵成的,从代码上来说,先创建交易还是先记账其实没有太大区别。但这事搁在包含了领域知识的架构里面可不行,因为账务要求,必须先有单据再有流水。
领域知识把程序员变成业务专家,渐渐的程序员就更能明白什么是业务、什么算框架,也可以开始用业务接口包装子系统,理解子系统之间的逻辑,让架构日臻完善。
P.S. 上面这些文字并没有涉及具体的设计原则、方法。
P.S.S. 再完美的架构也需要持续改进,况且这世界上恐怕还不存在所谓“完美”的架构。
分享/收藏

一段丑陋代码的诞生记

话说某年某月某日我在用PHP为我的服务器程序写测试代码,为了方便判断服务器的输出返回,我写下了下面的代码。

function assert_rpc_call($cmd, $input, $logid, $expected_err_code = 0) {
    $ret = rpc_call($cmd, $input, $logid);
    return $ret['result'] === $expected_err_code;
}

在这里,rpc_call()是我测试用的一个函数,可以用来访问我的服务器,它的返回值是一个数组,其中一定有’result’域存放一个整型的返回值。
这个函数实在是太好用了,以至于我的测试函数每个地方都用这个函数而不用rpc_call()。我非常满意,觉得已经万事OK了。
世界上唯一不变的是变化。
后来,我发现学xUnit那样每个测试用例打个点表示成功实在是无趣,而且如果真出了什么错就完全没法调试,于是我增强了这个函数。

function assert_rpc_call($cmd, $input, $logid, $expected_err_code = 0) {
    $ret = rpc_call($cmd, $input, $logid);
这样一来,默认情况下我的assert_rpc_call每次都会把返回的信息打印出来,成功输出绿色的PASS,失败返回红色的FAIL,煞是好看。我非常满意。
    $output = “”;
    if ($ret['result'] === $expected_err_code) {
        $output .= “\033[1;32mPASS\033[m\n";
    } else {
        $output .= "\033[1;31mFAIL\033[m\n";
    }
    $output .= print_r($ret, true);
    echo $output;
    return $ret;
}
工具如果太好用了,它一般会被滥用。
可是需求还在变化。不久,我发现有时候我为了初始化一个用于自动测试的环境需要调用很多肯定不会失败的接口,每次把它们的返回打印出来实在是扰乱视听,我需要有一个开关切换回原来的assert_rpc_call()行为。于是我加了一个参数和一些丑陋的代码实现这个功能。再接着,我又想在某些情况下获得返回的数组,在另外一些情况下返回bool值,而且还要在某些情况下打印输入的参数……我知道把这些矛盾的功能全部加到一个函数里面是非常丑陋的,也许应该拆成几个函数更为合适,但因为这个函数已经在很多地方调用,我实在懒得把每个地方都改好。
最终,代码变成了这样。

function [...]

设计哲学:职责单一还是接口单一

今天跟康神讨论设计,他提到一个设计哲学(这个词就很让人崇拜),很是有道理。大概意思是说,在设计中,有人喜欢职责单一,所有东西分的很细,有人喜欢接口单一,保证接口稳定,这是个设计哲学问题,各有优缺。
我自己是一个极度喜欢保持职责单一的人,面对康神设计的接口单一但内部逻辑较复杂的模块,多少有点想重构的冲动。其实这两种设计哲学都没太大问题,确实得看需求来确定到底用什么。
接口单一的设计,最典型例子的就是大家都知道的printf,这个东西博大精深,可以组合打印任意格式的文字,如果C标准委员会乐意,还可以通过扩展格式字符串来实现更多的打印选项,同时保证100%向下兼容。还有很多接口,它们通过传递一个结构来传递参数,这个结构拥有众多成员,每次调用接口时只用其中一部分。这些接口也能像printf一样易扩展,康神设计的就是后面这种接口。
这样设计的好处是保持整个系统接口的统一和稳定,这对于一个大型工业系统来说至关重要,因为牵一发动全身,改变任何一个通用接口都会付出很大代价。但它的缺点也十分明显,主要体现是效率不高和模块职责不明。
可以想象,printf的运行效率并不高,对于%以及后面参数的解析是在运行时完成的,每次运行都会在解析格式字符串上花时间。传结构的缺点是内存使用效率不高,并且随着业务逻辑不断变复杂,结构会大到不能接受的地步,最终肯定在结构中设置一个类似于格式字符串或者表达式的东西,逐渐转变成printf的设计模式。
同时,由于接口过于强大,在接口背后隐藏的逻辑也必定复杂无比。想想解析格式字符串或解析结构时该需要多少烦人的if…else,就知道要实现这种设计将会写出多少逻辑复杂的代码,会产生多少的bad smell,给后来维护的人造成不少困扰,甚至会引入不少潜在的错误。
反观职责单一的设计,没有接口单一设计的缺点,但劣势也很明显,就是没有统一的接口,使得整个系统模块之间的调用方式始终难以稳定。如果整个系统由一个人开发还好,如果多人协作,则会大大降低开发效率,会将大量时间用在沟通接口和解决联调问题上面。公平的说,职责单一的设计也可以用facade模式来提供统一接口,但是facade模式的实现代码又会变得和接口统一时一样,没有根本改变,只是一个折中和权衡。
那是否说明“完美”的职责单一的设计不适合大型开发呢?也不一定,因为我们有C++和无比变态的模板技巧。
比如我们可以设计出一个这样的print,功能类似于printf。注意,这个函数设计成只在print的时候才输出,expression类不会造成任何输出。

pattern p = builder() << “uid: ” << uid << ” uname: ” << name;
uid = 1000;
name = “realdodo”;
print(p); // 打印出”uid: 1000 uname: realdodo”

如果熟悉boost.spirit的朋友肯定觉得这个代码很眼熟,没办法,谁要我是这个库的忠实粉丝呢。在这里,pattern_builder类只是一个模板生成器,而不进行任何计算(不同于sstream这样的类),它用<<运算符针对各种类型生成各种特化的模板类,类似于一个嵌套的pair<>。这些模板类层层嵌套,但是pattern类可以轻松搞定,方法就是设计一个concrete_pattern类。

struct concrete_pattern_base
{
    concrete_pattern_base() {}
    virtual ~concrete_pattern_base() {}
    virtual string to_string() const = 0;
};

tempate <typename builder>
struct concrete_pattern : public concrete_pattern_base
{
    builder builder_;
    concrete_pattern(const builder & b)
        : builder_(b)
    {}

    virtual string to_string() const
    {
        // 用模板特化的方法实现to_string()方法,这里略去
    }
};

这样,pattern的实现就非常简单。

class pattern
{
    concrete_pattern_base * pattern_;

public:
    template <typename builder>
pattern(const builder [...]

IE的未来在何方

IE8即将发布已经不是新闻,很多评论家以及那些讨厌IE的开发者们都对这个亲近标准的IE抱有一丝好感,这也不是新闻。
IE8中最显著的改变莫过于对CSS 2.1的全面支持。正因为现在亲近标准而以前疏离标准,IE8引入了兼容模式这种复杂的新功能,来兼容以前那些不标准的网页。在外界看来,IE8这么痛苦的在标准支持和兼容性方面抉择,简直就是搬起石头砸自己的脚——早知现在,何必当初呢。
我曾经问过负责CSS 2.1标准支持的IE PM,为什么现在才开始做标准支持而以前不做,为什么不做DOM而选择CSS。他对此也感觉有心无力,毕竟无论是支持什么新标准,对IE来说都是一次大手术。IE是在标准产生之前确定的架构,现在能做到如此强大的功能还能支持那么多标准已经很不容易。就比如说对CSS 2.1的支持,IE重写了很多核心代码,花了大量的时间去保证正确性、性能和兼容性,已经有点精力不够。相比而言,还是开发WebSlice和 Accelerator更加省心省力。
现在的IE已经很有点像当年的NetScape。有谁能想到,当IE 5全面打败NetScape时,IE是世界上最标准的浏览器,它全面支持HTML 4和CSS 1,而且它还十分的小巧,比NetScape小一个数量级。IE的胜利绝对不仅仅因为它与Windows绑定,而是因为它实在比NetScape好太多了。NetScape当年拖着沉重的历史包袱,每一个升级都不敢做太大的变动,生怕那些使用了自己独有特性的网站出问题,至于标准,当然是抛在脑后。这何其像现在的IE。
时过境迁,多年之后,Firefox从当年NetScape的残骸之中诞生,Firefox成了当年的IE,IE成了当年的NetScape。当 Firefox一身轻松快速开发新版本的时候,IE还要考虑兼容IE6这样古董的浏览器。当Firefox毫无忌惮的通过自动更新强制用户更新浏览器版本的时候,IE还要修IE6的bug、还要发布IT工具方便众多公司阻止IE升级版本。唉,开发IE真累啊。
有很多人希望有朝一日Firefox能打败IE,解放现在身处“水深火热”之中的Web前端开发者。可是我觉得,这绝对不是未来的趋势,Firefox或者说Mozilla基金会绝不愿意这么做。浏览器世界的盟主不是那么好当的,站在风口浪尖,不但赚不到钱,还要花力气赔名声,得不偿失。
就拿修浏览器漏洞这件事情来说,Vista下的IE7已经做得非常安全,比Firefox好上无数倍,基本上对缓冲区溢出和DEP免疫了,可是大家还是会觉得Firefox更安全,根本原因是舆论导向。IE造好了一座坚固的城池,如果依然能将它攻破,对于攻击者来说,自然很有成就感。同时,新闻也总关注 IE的安全,毕竟它一出问题,全世界大部分电脑都处于危险之中,这绝对是大新闻。一来二去,IE不安全的口碑也就建立。至于Firefox究竟有多安全,那还真不见得。用XPCOM做的Firefox插件真的比ActiveX安全?还真不见得。从技术上来讲,做Firefox版的流氓插件不存在任何技术问题。
从我和IE PM聊天中得到的信息,IE现在最关注的领域是企业级应用和RIA。这两方面,恰好是Firefox最弱的地方。而在这个领域,IE真正的对手是 Google Chrome,所以下一个版本的IE会针对Google Chrome的特性采取很多动作。也许还要再经历3个版本,IE才能够脱胎换骨,不过那时,其他竞争者早不知道发展到什么程度了。对IE来说,是时候考虑学习一下Firefox,来个凤凰涅磐了。我猜,五年之后必有大变,我会拭目以待。
分享/收藏

什么是“Nabialek trick”?

现在是时候了,经过这些天的学习和尝试,我终于知道boost.spirit里谈到的Nabialek trick是什么东西了,当然应该大方的与大家分享。
这个Nabialek trick其实并不是编译期间的技巧,而是利用查表的方式将一些静态的类型推导变成简单的运行时调用,用性能损失换取编译期间的复杂,是一种相反的过程。可以说,Nabialek trick给人的感觉是完完全全的没有意义,要知道C++的template就是为了提升运行时性能而增加编译期间运算,这个反其道而行意义何在?其实说白了,意义就在于:消除模板技巧带来的疯狂的自动类型推导,以及解除对某些类型的静态绑定。前者意义在于可以提升编译速度,而且也可以避免达到模板嵌套上限(比如MSVC 7.1,最多只能嵌套256层,这已经算不错了),后者意义在于可以让复杂的表达式变得更容易匹配、更快的执行和更加灵活(所谓的将“线性非决定式”转化为“决定式”,将“或”逻辑从表达式中剔除,避免不必要回溯以加快速度),但是由于创建查找表的过程涉及到内存分配,所以后者并不能总是达到很好效果。
我们再从正面来想想Expression Templates给编程带来的困难,请看下面的示例代码(来自boost.spirit的帮助/libs/spirit/doc/techniques.html,我做了一些简化和修改):

r =
“//” >> *(anychar_p – ‘n’) >> ‘n’
| “/*” >> *(anychar_p – “*/”) >> “*/”
;

在这里,r的类型就是:

alternative<sequence<sequence<strlit<const char*>, kleene_star<difference<anychar_parser, chlit<char> > > >, chlit<char> >, sequence<sequence<
strlit<const char*>, kleene_star<difference<anychar_parser, strlit<const char*> > > >, strlit<const char*> > >

是的,我没指望任何人能够看懂……不过假如做一下的赋值:

line1 = “//” >> *(anychar_p – ‘n’) >> ‘n’;
line2 = “/*” >> *(anychar_p – “*/”) >> “*/”;
r = line1 | line2;

这样的代码应该好理解一些,但是没有本质不同。再来改改:

line1 [...]

boost中如何面对疯狂的类型推导?

这些天,我一直在说一个名词:Expression Templates。这个东西很有用,我也会在未来把这篇英文文档用易懂的中文讲解一遍(当然,各位已经看过的高手可以无视我未来的这篇文章)。
可以很肯定地说,Expression Templates一定是未来C++各种类库设计的标准思路,现在boost.spirit、boost.lambda、boost.uBLAS、boost.xpressive等库都是用这种思路设计,而且介绍这种方法的论文也明确指出,合理应用这种方法可以显著的提高程序性能,能让C++程序库的效率高于相同功能的C程序库(例如std::sort和qsort的比较)。但是它的一个致命缺点是:令人发狂的类型推导。
看看下面这个例子吧,它是boost.spirit的一个例子(boost 1.34.0的libs/spirit/example/techniques/no_rules/no_rule2.cpp),用于说明如果没有自动类型推导或者类似功能的机制,类型定义会变得多么的疯狂:

程序代码
struct skip_grammar : grammar<skip_grammar>
{
    template <typename ScannerT>
    struct definition
    {
        definition(skip_grammar const& /*self*/)
        : skip
            (       space_p
                |   ”//” >> *(anychar_p - ’\n’) >> ’\n’
                |   ”/*” >> *(anychar_p - ”*/”) >> ”*/”
            )
        {
        }
        typedef
           alternative<alternative<space_parser, sequence<sequence<
           strlit<const char*>, kleene_star<difference<anychar_parser,
           chlit<char> > > >, chlit<char> > >, sequence<sequence<
           strlit<const char*>, kleene_star<difference<anychar_parser,
           strlit<const char*> > > >, strlit<const char*> > >
        skip_t;
        skip_t skip;
        skip_t const& start() const { return skip; }
    };
};
请注意中间的那个skip_t的类型定义,我相信一定没有人相信是人手写出来的……本来应该编译器做的事情换做人来做,太疯狂了!特别是还要自己弄明白所有的operator的返回类型,更加的困难……
boost.spirit的作者Joel de Guzman给出了一个比较猥琐的解决方案:将skip_t先定义成随便一个什么类型,比如int,然后编译,这样便一起就会提示说:“Cannot convert boost::spirit::alternative<… …………> to int”,把这个错误拷贝出来,就可以看到实际的类型了——天哪,足够疯狂了!这还没完,如果遇到更复杂的情况怎么办,看看boost 1.34.0的libs/spirit/example/techniques/no_rules/no_rule3.cpp吧,头开始疼了……
所以,Joel de Guzman在给我留下深刻印象之后开始说明他的方案。
方案一:期待C++ 0X早日成为事实的标准,让那个非常有用的auto关键字成为到处可用的东西。
方案二:使用部分编译器已经实现的typeof关键字,然后写一个宏:

程序代码
#define RULE(name, definition) typeof(definition) name = definition

这样,就可以简单的使用宏来简化各种类型定义,不过可怜的MSVC并没有实现这种功能。
方案三:使用“Nabialek trick”。看到“Nabialek trick”这个陌生的名词,我的第一个反应就是“去google/baidu”,不过很可惜,google上的所有搜索结果都直接或间接的来自我正在看的boost.spirit文档,而baidu更是告诉我找不到。OK,我们还是回到这个名词本身吧。这个trick是因为其发明者,Sam Nabialek,而得名,其作用是将“线性非决定式”(linear non-deterministic)转换成“决定式”(deterministic)。如果要说的更具体一些就不行了,其实我还不太明白,我正在阅读boost.spirit的部分源码,希望能够有些收获。无论如何,boost.spirit最终用极为优雅的方式将类型推导的难题再次交给了编译器,整个代码非常的简洁明了,可谓是最佳的实现方式。
就我个人来说,应该还可以提出方案四:使用boost.typeof库的基础设施,不过为了能使用这个东西,依然必须手动的“注册”各种类型,并依赖于大量的模板和宏技巧,比较烦,所以并不算一个很好的方式,但至少还可以接受。
综合来说,正是因为Expression Templates的广泛使用,类型推导成了一个疯狂的怪兽,而C++ 0X中的auto关键字成了绝对的救星,我们还是来呼唤新标准早日降临吧!
分享/收藏

使用boost.spirit制作一个简单的四则计算器

从传统意义上来说,boost.spirit库是一个类似于yacc的库,主要业务是做词法解析,然后提供各种读取数据的接口,但是由于这是一个用C++实现、并且大量运Expression Templates技巧的库,所以各种功能可以用非常快捷的方式实现,非常的好用,几乎把C++的各种优良特性都充分发挥出来。
在此,我就从boost.spirit库中间的一个例子出发,经过简单的修改就变成一个极为健壮的四则计算器。我所参考的例子是boost 1.34.0的libs/spirit/example/fundamental/calc_plain.cpp,我因为是在这上面直接修改而来,所以源码中带有原作者的版权声明。
关于boost.spirit的用法,在这里我先不说,以后有时间我来慢慢的把它用中国话讲解一遍。这个程序的核心实际上是一个EBNF的表达式,也就是如何用EBNF语法来表示四则运算。
在这里,我就直接给出答案(EBNF的知识请暂时自行看编译原理的教材):

程序代码
expression ::= term ( (‘+’ term) | (‘-’ term) )*
term ::= factor ( (‘*’ factor) | (‘/’ factor) )*
factor ::= REAL | ’(‘ ex ’)’ | (‘-’ factor) | (‘+’ factor)
其中,expression就是我们需要的表达式pattern。注意这里,正是由于expression首先去匹配term,而term首先去匹配factor,最后factor是以“纯数字”、“括号”、“正负符号”的顺序匹配,term则是以factor、“乘法”、“除法”的顺序匹配,而expression是以term、“加法”、“减法”的顺序匹配,所以就保证了整个表达式匹配过程是按照四则运算的先后顺序进行的。在匹配的过程中只要安插各种“监视”的函数(boost.spirit里面的术语叫做“actor”),就可以轻松实现四则运算。
把EBNF对应到boost.spirit里,具体的grammer类实现如下:

程序代码
struct calculator : public grammar<calculator>
{
    template <typename ScannerT>
    struct definition
    {
        definition(calculator const& /*self*/)
        {
            expression
                =   term
                >> *(   (‘+’ >> term[do_calc<do_add>()])
            |   (‘-’ >> term[do_calc<do_substract>()])
            )
                ;
            term
                =   factor
                >> *(   (‘*’ >> factor[do_calc<do_multiply>()])
            |   (‘/’ >> factor[do_calc<do_divide>()])
            )
                ;
            factor
                =   real_p[&push_real]
            |   ’(‘ >> expression >> ’)’
                |   (‘-’ >> factor[&do_neg])
            |   (‘+’ >> factor)
                ;
        }
        rule<ScannerT> expression, term, factor;
        rule<ScannerT> const&
            start() const { return expression; }
    };
};

请注意,calculator从grammer派生,但是除了在内部定义了一个嵌套class以外,并没有做更多的事情。
至于这里面用到的do_calc<>、push_real等functor和函数,则是一些极为简单的东西,实现如下:

程序代码
namespace
{
    stack<double> calc_stack;
    struct do_add
    {
        double operator () (double lhs, double rhs) const
        {
            return lhs + rhs;
        }
    };
    struct do_substract
    {
        double operator () (double lhs, double rhs) const
        {
            return lhs - rhs;
        }
    };
    struct do_multiply
    {
        double operator () (double lhs, double rhs) const
        {
            return lhs * rhs;
        }
    };
    struct do_divide
    {
        double operator () (double lhs, double rhs) const
        {
            return lhs / rhs;
        }
    };
    template <typename op>
    struct do_calc
    {
        void operator () (const char *, const char *) const
        {
            double result = calc_stack.top();
            calc_stack.pop();
            result = op()(calc_stack.top(), result);
            calc_stack.pop();
            calc_stack.push(result);
        }
    };
    void push_real(double d)
    {
        calc_stack.push(d);
    }
    void do_neg(char const*, char const*)  
    {
        cout << ”NEGATE\n”;
        double result = calc_stack.top();
        calc_stack.pop();
        calc_stack.push(-result);
    }
    double show_result()     
    {
        return calc_stack.top();
    }
}

可以从源码中清晰看到,这些函数就是简单的做了些出栈/入栈以及运算的工作,每一个函数功能极为单纯,可认为就是一些状态及处理函数而已,而状态机逻辑则由grammer搞定了。
分享/收藏

令人害怕的C++错误输出

这几天详细阅读了boost不少库的文档,特别详细研究了一下boost.spirit库,一个非常令人惊叹的LL词法分析库。关于这个库本身,我以后有时间再慢慢介绍,真的非常让人惊叹,不过今天我要惊叹的是使用它时得到的错误信息……
我用VS2005编译,错误信息如下:

程序代码
d:\boost\boost_1_34_0\boost\spirit\core\scanner\scanner.hpp(130) : error C2198: ’void (__cdecl *const )(const char *,const char *)’ : too few arguments for call
        d:\boost\boost_1_34_0\boost\spirit\core\scanner\scanner.hpp(161) : see reference to function template instantiation ’void boost::spirit::attributed_action_policy<AttrT>::call<void(__cdecl *)(const char *,const char *),IteratorT>(const ActorT &,const double &,const IteratorT &,const IteratorT &)’ being compiled
        with
        [
            AttrT=const double,
            IteratorT=iterator_t,
            ActorT=void (__cdecl *)(const char *,const char *)
        ]
        d:\boost\boost_1_34_0\boost\spirit\core\composite\actions.hpp(109) : see reference to function template instantiation ’void boost::spirit::action_policy::do_action<void(__cdecl *)(const char *,const char *),const T,iterator_t>(const ActorT &,AttrT &,const IteratorT &,const IteratorT &) const’ being compiled
        with
        [
            T=double,
            ActorT=void (__cdecl *)(const char *,const char *),
            AttrT=double,
            IteratorT=iterator_t
        ]
        d:\boost\boost_1_34_0\boost\spirit\core\composite\alternative.hpp(60) : see reference to function template instantiation ’boost::spirit::match<T> boost::spirit::action<ParserT,ActionT>::parse<ScannerT>(const ScannerT &) const’ being compiled
        with
        [
            T=double,
            ParserT=boost::spirit::real_parser<double,boost::spirit::real_parser_policies<double>>,
            ActionT=void (__cdecl *)(const char *,const char *),
            ScannerT=boost::spirit::scanner<const char *,boost::spirit::scanner_policies_t>
        ]
        d:\boost\boost_1_34_0\boost\spirit\core\composite\alternative.hpp(60) : see reference to function template instantiation ’boost::spirit::match<boost::spirit::nil_t> boost::spirit::alternative<A,B>::parse<ScannerT>(const ScannerT &) const’ being compiled
        with
        [
            A=boost::spirit::action<boost::spirit::real_parser<double,boost::spirit::real_parser_policies<double>>,void (__cdecl *)(const char *,const char *)>,
            B=boost::spirit::sequence<boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::rule<scanner_t>>,boost::spirit::chlit<char>>,
            ScannerT=boost::spirit::scanner<const char *,boost::spirit::scanner_policies_t>
        ]
        d:\boost\boost_1_34_0\boost\spirit\core\composite\alternative.hpp(60) : see reference to function template instantiation ’boost::spirit::match<boost::spirit::nil_t> boost::spirit::alternative<A,B>::parse<ScannerT>(const ScannerT &) const’ being compiled
        with
        [
            A=boost::spirit::alternative<boost::spirit::action<boost::spirit::real_parser<double,boost::spirit::real_parser_policies<double>>,void (__cdecl *)(const char *,const char *)>,boost::spirit::sequence<boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::rule<scanner_t>>,boost::spirit::chlit<char>>>,
            B=boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::action<boost::spirit::rule<scanner_t>,void (__cdecl *)(const char *,const char *)>>,
            ScannerT=boost::spirit::scanner<const char *,boost::spirit::scanner_policies_t>
        ]
        d:\boost\boost_1_34_0\boost\spirit\core\non_terminal\impl\rule.ipp(233) : see reference to function template instantiation ’boost::spirit::match<boost::spirit::nil_t> boost::spirit::alternative<A,B>::parse<ScannerT>(const ScannerT &) const’ being compiled
        with
        [
            A=boost::spirit::alternative<boost::spirit::alternative<boost::spirit::action<boost::spirit::real_parser<double,boost::spirit::real_parser_policies<double>>,void (__cdecl *)(const char *,const char *)>,boost::spirit::sequence<boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::rule<scanner_t>>,boost::spirit::chlit<char>>>,boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::action<boost::spirit::rule<scanner_t>,void (__cdecl *)(const char *,const char *)>>>,
            B=boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::rule<scanner_t>>,
            ScannerT=boost::spirit::scanner<const char *,boost::spirit::scanner_policies_t>
        ]
        d:\boost\boost_1_34_0\boost\spirit\core\non_terminal\impl\rule.ipp(232) : while compiling class template member function ’boost::spirit::match<boost::spirit::nil_t> boost::spirit::impl::concrete_parser<ParserT,ScannerT,AttrT>::do_parse_virtual(const ScannerT &) const’
        with
        [
            ParserT=boost::spirit::alternative<boost::spirit::alternative<boost::spirit::alternative<boost::spirit::action<boost::spirit::real_parser<double,boost::spirit::real_parser_policies<double>>,void (__cdecl *)(const char *,const char *)>,boost::spirit::sequence<boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::rule<scanner_t>>,boost::spirit::chlit<char>>>,boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::action<boost::spirit::rule<scanner_t>,void (__cdecl *)(const char *,const char *)>>>,boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::rule<scanner_t>>>,
            ScannerT=boost::spirit::scanner<const char *,boost::spirit::scanner_policies_t>,
            AttrT=boost::spirit::nil_t
        ]
        d:\boost\boost_1_34_0\boost\spirit\core\non_terminal\rule.hpp(135) : see reference to class template instantiation ’boost::spirit::impl::concrete_parser<ParserT,ScannerT,AttrT>’ being compiled
        with
        [
            ParserT=boost::spirit::alternative<boost::spirit::alternative<boost::spirit::alternative<boost::spirit::action<boost::spirit::real_parser<double,boost::spirit::real_parser_policies<double>>,void (__cdecl *)(const char *,const char *)>,boost::spirit::sequence<boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::rule<scanner_t>>,boost::spirit::chlit<char>>>,boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::action<boost::spirit::rule<scanner_t>,void (__cdecl *)(const char *,const char *)>>>,boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::rule<scanner_t>>>,
            ScannerT=boost::spirit::scanner<const char *,boost::spirit::scanner_policies_t>,
            AttrT=boost::spirit::nil_t
        ]
        e:\realdodo\my project\testboostwavequickstart\cacl_plain.cpp(130) : see reference to function template instantiation ’boost::spirit::rule<T0> &boost::spirit::rule<T0>::operator =<boost::spirit::alternative<A,B>>(const ParserT &)’ being compiled
        with
        [
            T0=scanner_t,
            A=boost::spirit::alternative<boost::spirit::alternative<boost::spirit::action<boost::spirit::real_parser<double,boost::spirit::real_parser_policies<double>>,void (__cdecl *)(const char *,const char *)>,boost::spirit::sequence<boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::rule<scanner_t>>,boost::spirit::chlit<char>>>,boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::action<boost::spirit::rule<scanner_t>,void (__cdecl *)(const char *,const char *)>>>,
            B=boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::rule<scanner_t>>,
            ParserT=boost::spirit::alternative<boost::spirit::alternative<boost::spirit::alternative<boost::spirit::action<boost::spirit::real_parser<double,boost::spirit::real_parser_policies<double>>,void (__cdecl *)(const char *,const char *)>,boost::spirit::sequence<boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::rule<scanner_t>>,boost::spirit::chlit<char>>>,boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::action<boost::spirit::rule<scanner_t>,void (__cdecl *)(const char *,const char *)>>>,boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::rule<scanner_t>>>
        ]
        e:\realdodo\my project\testboostwavequickstart\cacl_plain.cpp(109) : while compiling class template member function ’calculator::definition<ScannerT>::definition(const calculator &)’
        with
        [
            ScannerT=scanner_t
        ]
        d:\boost\boost_1_34_0\boost\spirit\core\non_terminal\impl\grammar.ipp(262) : see reference to class template instantiation ’calculator::definition<ScannerT>’ being compiled
        with
        [
            ScannerT=scanner_t
        ]
        d:\boost\boost_1_34_0\boost\spirit\core\non_terminal\impl\grammar.ipp(281) : see reference to function template instantiation ’void boost::spirit::impl::call_helper<0>::do_<result_t,definition_t,ScannerT>(RT &,DefinitionT &,const ScannerT &)’ being compiled
        with
        [
            ScannerT=scanner_t,
            RT=result_t,
            DefinitionT=definition_t
        ]
        d:\boost\boost_1_34_0\boost\spirit\core\non_terminal\grammar.hpp(53) : see reference to function template instantiation ’boost::spirit::match<boost::spirit::nil_t> boost::spirit::impl::grammar_parser_parse<0,calculator,boost::spirit::parser_context<>,ScannerT>(const boost::spirit::grammar<DerivedT> *,const ScannerT &)’ being compiled
        with
        [
            ScannerT=scanner_t,
            DerivedT=calculator
        ]
        d:\boost\boost_1_34_0\boost\spirit\core\non_terminal\grammar.hpp(63) : see reference to function template instantiation ’boost::spirit::match<boost::spirit::nil_t> boost::spirit::grammar<DerivedT>::parse_main<ScannerT>(const ScannerT &) const’ being compiled
        with
        [
            DerivedT=calculator,
            ScannerT=scanner_t
        ]
        d:\boost\boost_1_34_0\boost\spirit\core\scanner\impl\skipper.ipp(131) : see reference to function template instantiation ’boost::spirit::match<boost::spirit::nil_t> boost::spirit::grammar<DerivedT>::parse<scanner_t>(const ScannerT &) const’ being compiled
        with
        [
            DerivedT=calculator,
            ScannerT=scanner_t
        ]
        d:\boost\boost_1_34_0\boost\spirit\core\scanner\impl\skipper.ipp(153) : see reference to function template instantiation ’boost::spirit::parse_info<> boost::spirit::impl::phrase_parser<boost::spirit::space_parser>::parse<IteratorT,DerivedT>(const IteratorT &,const IteratorT &,const ParserT &,const boost::spirit::space_parser &)’ being compiled
        with
        [
            IteratorT=const char *,
            DerivedT=calculator,
            ParserT=calculator
        ]
        d:\boost\boost_1_34_0\boost\spirit\core\scanner\impl\skipper.ipp(171) : see reference to function template instantiation ’boost::spirit::parse_info<> boost::spirit::parse<const CharT*,DerivedT,boost::spirit::space_parser>(const IteratorT &,const IteratorT &,const boost::spirit::parser<DerivedT> &,const boost::spirit::parser<boost::spirit::space_parser> &)’ being compiled
        with
        [
            CharT=char,
            DerivedT=calculator,
            IteratorT=const char *
        ]
        e:\realdodo\my project\testboostwavequickstart\cacl_plain.cpp(160) : see reference to function template instantiation ’boost::spirit::parse_info<> boost::spirit::parse<_Elem,DerivedT,boost::spirit::space_parser>(const CharT *,const boost::spirit::parser<DerivedT> &,const boost::spirit::parser<boost::spirit::space_parser> &)’ being compiled
        with
        [
            _Elem=char,
            DerivedT=calculator,
            CharT=char
        ]

我数了一下,这个错误信息总共有11355个字符,并且完全没有告诉我究竟应该如何排除这个错误……不过幸好在错误信息的最后几行中指出了真正出错的地方(真的是这样,前面的输出完全是没有用的烟雾弹……),然后利用这一点点信息,结合boost.spirit的文档,我终于知道错误的原因:我把一个函数的输入参数类型和个数写错了。
OK,看来调试这种基于Expression Templates的库真的需要耐心和勇气。
分享/收藏

C++中的delegate机制:现在网上可以找到的解决方案

说道C++中的delegate机制,在网上已经有很多人给出了自己的实现,我觉得最值得借鉴的有两篇,第一个是Yingle Jiang(蒋迎乐)在CodeProject上面发表的Yet Another C#-style Delegate Class in Standard C++(http://www.codeproject.com/cpp/acfdelegate.asp),另一个是Don Clugston在CodeProject上写的Member Function Pointers and the Fastest Possible C++ Delegates – The Code Project – C++ / MFC(http://www.codeproject.com/cpp/FastDelegate.asp)。
对于第一篇,Yingle Jiang所实现的delegate和我所想的方案非常类似,当然,他实现了更多更强大的功能,做的比我完善的多。因此,今天仔细看了他的解决方案之后就觉得没有必要再做下去了,在这种思路之下我暂时没有想到有什么值得改进的地方。值得一提的是,他自己在SourceForge上面做了一个名为Another C++ Framework的开源项目,目的是将.Net Framework中的一些基础设施引入到C++开发中去,这就包括delegate和EventHandler,我觉得今后如果要用到类似的基础设施完全可以从他那里获得经验并且移植。
对于第二篇,Don Clugston则用一种依赖于编译器的方式实现了高效的成员函数指针调用和C++的delegate机制。简单的说,他利用各种编译器在各种平台上生成的汇编代码的特点,将C++中神秘的成员函数调用还原普通的函数调用,其调用的汇编代码用他的框架在运行时“生成”,使任何函数都可以无差别的挂接在他的delegate之上。可以看出,他为了实现这个机制对很多种编译器在很多平台上的表现都做过研究,其艰辛可想而知。
总而言之,我已经没必要花时间实现更完善的delegate和EventHandler机制,我将更多的关注插件机制,自己做一个简单可用的东西再说。
分享/收藏