Archive

Archive for the ‘translation’ Category

[翻译]Python HTML 解析器性能评测

August 25th, 2010 yaker 2 comments

原文:http://blog.ianbicking.org/2008/03/30/python-html-parser-performance/
作者:Ian Bicking
译者:Yaker Gong
本文链接:http://www.yakergong.com/blog/?p=487 转载请保留出处

在准备我的 PyCon上关于HTML的演讲 的时候我觉得我应该对现有的一些解析器和文档模型做个性能对比。

实际上,情况有点复杂,因为处理HTML需要几个步骤:

  1. 解析这个 HTML
  2. 把它解析为一个对象(比如一个文档对象)
  3. 把它序列化

有些解析器只处理第一步,有些只处理第二步,有些能处理所有的三个步骤…。例如,ElementSoup 使用 ElementTree 来表示文档,却使用 BeautifulSoup 作为实际的解析器。而 BeautifulSoup 内部也拥有一个文档对象。 HTMLParser 仅仅做解析(不解析出任何对象),然而 html5lib 却能够生成几种不同的文档树(DOM树)。序列化也分为XML和HTML两种方式。

所以我选取了下面这些解析器的库做基准性能测试:

  • lxml:包含一个解析器,能够产生文档对象,支持HTML序列化。它也可以不适用内置的解析器而使用 BeautifulSoup 或者 html5lib 进行解析。
  • BeautifulSoup:nbsp;包含一个解析器,能够产生文档对象,支持HTML序列化。
  • html5lib:有解析器。它也有一个序列化器,但是我没有使用它。它也有一个内置的文档对象(即simpletree),只是…除了自我测试我也不知道这东西还能做什么。
  • ElementTree:这个包里有一个XML序列化器,ElementTree能够产生文档对象,它也是python内置的XML解析模块。(我觉得下个版本会带一个HTML序列化器,不过我也没测试这个XML序列化器)。它也有一个解析器,测试的时候我用html5lib当做解析器来测试ElementTree的。
  • cElementTree:这是一个使用C语言扩展实现的python模块,实现了ElementTree。
  • HTMLParser:包含一个解析器。但是其实它不能解析出文档对象,很多正常网页都不能正常处理(包含Table或者Script),有语法错误的网页就更处理不了了。它只是使用解析器遍历文档。
  • htmlfill:它使用了HTMLParser作为解析器,相对HTMLParser,它在解析过程中对Element做了更多处理。
  • Genshi[1]:包含一个解析器,能够产生文档对象,支持HTML序列化。
  • xml.dom.minidom:python标准库里的内置文档模型,html5lib 能够解析出这种文档对象。(我并不推荐使用minidom — 这篇文章里写了一些理由,还有很多理由我没写出来)

我预想 lxml 的性能会比较好,因为它基于 libxml2这个C库。但是实际上它的性能比我预计的还要好,超过其它所有的同类库。所以,除非考虑到一些难以解决的安装问题(尤其是在Mac上),我都推荐你用lxml 来进行HTML解析的工作。

我的测试代码在这里,你可以自己下载下来运行测试程序。里面包含了所有的样例数据,用来生成图表的命令在这里。这些测试数据来自于从 python.org 随机选取的一些页面(总共355个)。

解析

lxml:0.6; BeautifulSoup:10.6; html5lib ElementTree:30.2; html5lib minidom:35.2; Genshi:7.3; HTMLParser:2.9; htmlfill:4.5

第一个测试运行这些解析器解析文档。需要注意的是:lxml 比 HTMLParser快6倍,尽管 HTMLParser
不生成任何文档对象(lxml在内存中建立了一个文档树)。这里也没有包含 html5lib 所能生成的全部种类的树,因为每一种花费的时间都差不多。之所以包含了使用 xml.dom.minidom 作为输出结果的 html5lib 测试结果是为了说明 minidom 有多慢。Genshi确实很快,只是它也是最不稳定的,相比之下,html5lib , lxml 以及 BeautifulSoup 都要健壮的多。html5lib 的好处是,总是能够正确的解析HTML(至少在理论上如此)。

lxml在解析过程中会释放 GIL ,但是我觉得应该影响不大。

序列化

lxml:0.3; BeautifulSoup:2.0; html5lib ElementTree:1.9; html5lib minidom:3.8; Genshi:4.4

所有这些库执行序列化都很快,可是 lxml 又一次遥遥领先。ElementTree 和 minidom 只做XML序列化,但是没有理由说HTML序列化更快。还有就是,Genshi居然比minidom要慢,实话说任何比minidom要慢的东西都挺让人震惊的。

内存占用

lxml:26; BeautifulSoup:82; BeautifulSoup lxml:104; html5lib cElementTree:54; html5lib ElementTree:64; html5lib simpletree:98; html5lib minidom:192; Genshi:64; htmlfill:5.5; HTMLParser:4.4

最后一项测试是内存。我并不是特别确信我做这个测试的方法很科学,但是数据总能说明一些问题。这项测试会解析所有的文档并把解析出来的DOM树保存在内存中,利用 ps 命令结果的RSS(resident set size)段来表示进程占用的内存。计算基准内存占用之后所有的库已经被import,所以只有解析HTML和生成文档对象会导致内存使用量上升。

我才用 HTMLParser 作为基准线,因为它把文档保存在内存中,只产生一些中间字符串。这些中间字符串最终也不回占用多少内存,因为内存占用基本上等同于这些html问价大小之和。

测量过程中有个棘手的问题就是python的内存分配器并不会释放它请求的内存,所以,如果一个解析器创建了很多中间对象(字符串等等)然后又释放了它们,进程仍然会持有这些内存。为了检测是否有这种情况,我试着分配一些新的字符串知道进程占用的内存增长(检测已经分配但是没有被使用的内存),但是实际上没检测到什么,只有 BeautifulSoup 解析器,在序列化到一个 lxml 树的时候,显示出使用了额外的内存。

只有在内存测试中,html5lib 使用 cElementTree 来表示文档对象同使用 ElementTree 能表现出明显的不同。我倒不是很惊讶,我猜因为我没有找到一个C语言编写的序列化工具,我猜使用 cElementTree 构建文档树的话,只有在用本地代码调用它的时候比较快(就像本地的libxml,并且不需要把数据结构传递到python中)。

lxml比较节省内存很可能是因为它使用了本地的libxml2的数据结构,并且只有在需要的时候才创建Python对象。

总结

在进行基准测试之前我就知道lxml会比较快,但是我自己也没料到会这么快。

所以呢,总结一下:lxml太牛逼了[2]。你可以用很多种方式使用它,你可以对一个HTML进行解析,序列化,解析,再序列化,在机器卡机之前你能重复这些操作很多次。很多操作都是通过本地接口实现的,python只做了一层很浅的封装。例如,如果你做一次XPath查询,查询字符串会被编译为本地代码,然后遍历本地的libxml2对象,只在返回查询结果的时候才会产生一个python对象。 另外,测试中lxml内存占用比较小使我更有理由相信lxml在高负载的情况下仍然会很可靠。

我觉得,文档树相对按字符流解析(不生成树,只扫描一次文档并针对特定的标签做处理)更有优势。表面看起来按字符流解析更好:你不把整个文档放在内存里,处理的时间之和文档大小线性相关。HTMLParser就是这样一种解析器,遇到各种符号(标签开始和关闭,变迁中间的文字等等)。Genshi 也是用的这个模型,因为使用了一些更高级的特性(比如 filters
)所以使用起来更自然一些。其实字符流模型本身就不是一种特别自然的处理XML文档的方式,从某种程度上说,它只是用来处理一些本来就可以当做字符串处理的文档的一种笨拙的方法(regex可以实现同样的功能)。只有你需要处理上G的XML文件的时候按字符流解析才有意义(不过lxml和ElementTree针对这种情况都有额外的参数支持)。HTML文件不会有这么大,这些测试也有理由让我们相信lxml可以很好的处理大的HTML文件,所以一个大文档也不会导致一个为小文档优化过的系统崩溃。

Ian Bicking on Sunday, March30th, 2008

[1]. Genshi是EdgewallSoftware的产品,它的其他产品还包括大名鼎鼎的Trac。

[2]. 本文的作者Ian Bicking是lxml.html(lxml的一个模块)的开发者和维护者(这里修正一下)。

P.S. 译者记:这里还有一个解析器没有提到就是python标准库里的SGMLParser,它也可以产生ElementTree,但是性能很差,本机测试解析600k的html文档(ddd的单页html文档)需要480秒,不推荐应用在性能要求比较高的场合。本文作者也是lxml的作者,对自己的作品大力推荐也是正常的,我实测过lxml性能确实很好。

Categories: python, translation Tags:

WTL教程第十章 – 支持拖放操作

January 4th, 2010 yaker 3 comments

译文见 http://yakergong.com/wtl/PartX.htm
这是WTL教程的作者mike在06年更新的

翻译这篇文章用了omegat做辅助翻译工具。omegat 2.0.5的版本中文显示仍然有问题,linux系统下可以用如下方法解决,首先切换到程序所在目录(前提是使用sourceforge上下载的二进制包,使用包管理器安装的版本需要自己修改java的fontconfig)

mkdir jre/lib/fonts/fallback
cd jre/lib/fonts/fallback
ln -s /usr/share/fonts/truetype/arphic/uming.ttc

Categories: translation Tags:

[翻译]使你的C/C++代码支持Unicode

April 22nd, 2009 yaker No comments

译文见 http://www.i18nguy.com/unicode/c-unicode.zh-CN.html
原文见 http://www.i18nguy.com/unicode/c-unicode.html

偶然读到i18nguy上的这篇文章的英文原文,虽然是几年之前的文章,但是觉得作为一个简明手册还是很有价值的。于是给文章的作者Tex Texin先生发了封邮件,得到texin先生的支持后便翻译了一下。

文章并不长,没有使用OmegaT这种辅助翻译的利器,只用了网页编辑器(Kompozer)。刚刚翻译完的时候,自己读了一遍,觉得有些地方很生硬,又润色了一下。之后看了下翻译相关的书籍,对翻译工作的艺术性有了点切身的了解。

Tex Texin先生是XenCraft公司首席架构师及全球化咨询师。Unicode Bulldog Award获得者(2004)。关于他更详细的介绍见这里

Categories: translation, unicode Tags: ,

[翻译]计算机科学教育:明日的软件工程师在哪里?

February 19th, 2008 yaker No comments

这是发表在The Journal of Defense Software Engineering上的一篇文章,翻译了一下。
原文链接:http://www.stsc.hill.af.mil/CrossTalk/2008/01/0801DewarSchonberg.html
英文原文pdf版
译稿pdf版

Jan 2008 Issue


计算机科学教育:明日的软件工程师在哪里?

Dr. Robert B.K. Dewar,  AdaCore Inc.
Dr. Edmond Schonberg,  AdaCore Inc.

我们认为目前的计算机科学教育忽视基础技能,尤其是在编程和形式化方法方面。我们认为普遍采用Java作为第一门编程语言是造成这一下滑的原因之一。我们简要的检查了作为每一个软件专家的全部技能的一部分的一组编程技能。
全部都是关于编程的!在过去的几年中我们注意到这些在计算机教育中令人烦恼的趋势。以下几条简要的概括了这些趋势:
1.在计算机科学项目中数学要求正在减少。
2.人们不再使用多种语言来发展编程技能,而是通过使用大型的库和专用的包来快速的入门。
3.这样做获得的技能对于现代的软件工业(尤其是对安全领域而言)来说是不够的,不幸的是,这恰好是外购工业所能提供的。我们在培养能够轻易被替代的专家。

这些趋势在美国计算机协会最新的推荐必修课程中明显地体现出来。2005年的必修课程甚至都没提到数学方面的先修科目,并且它只提及了编程语言理论中的一门课程[1]。
我们从两方面看到了这些变化。一方面,作为有数十年经验的纽约大学教职员工,我们后悔为大多数计算机专业的学生选择了Java作为第一门编程语言。我们已经看到这样的选择弱化了我们的学生的能力,就像他们在系统与结构课程中表现出来的一样。另一方面,作为一家专注于为使命攸关的系统提供Ada编程工具的公司的创始人,我们发现越来越难招聘到具有基本技能的合格的申请者。我们想要提倡一种更严格的课程结构,在这种结构中形式化方法能够及早地被介绍并且编程语言在其中据中心地位。

形式化方法和软件构建

20年前,证明程序正确性的技术是非常活跃的研究主题。但是,这些方法(和硬件)的时间开销阻碍了这些技术的大规模应用,并且它们也因此或多或少地被多数计算机科学项目忽略。这是很不幸的,因为这些技术引出了一个非常重要的事实,即他们能够被应用在大型的系统中并且实质性地改善系统的可靠性。举一个例子,就是在英国的“基于地面的航空交通控制系统”(参见一份关于iFACTS的介绍<www.nats.co.uk/article/90>)中使用了SPARK。SPARK是扩展的Ada的一个子集,它包含断言,短延时的设计者能够确保程序的一些重要属性,例如终止,缺少运行时异常,有限的内存占用等等。很明显,这样的设计与分析方法学(通过构建来保证正确性)有助于在本质上增加(从一开始就在设计上使用SPARK的)系统的可靠性。
另外一个计算机科学系的学生应该表现出来的形式化方法是模型检查和线性时序逻辑,这是设计并行系统所必需的。想要了解关于这一主题的一场现代的讨论(以一个使命攸关的软件为中心),参见 [3]。
我们发现浮点运算是计算机科学领域中另一个被忽视的领域。以前,在纽约大学,数值方法和浮点运算课程是必须的。但这个要求在多年以前被废弃了,现在只有很少的学生修这门课程。这一主题对于所有的科学或者工程软件来说都是至关重要的,并且在语法上的微妙的。我们会想象它应该是所有计算机科学课程的必须部分,但是(我们)总是把Matlab当作统一的编程工具并将完全忽视了这些主题。

采用Java作为第一门语言的缺点

由于在Web应用中的流行和使得初学者能容易地创建图形程序的便利性,Java,成为了介绍编程语言的课程中最为广泛使用的语言。我们认为这是使编程更为有趣的一种误导性的尝试,也许是这是对计算机科学(学院)招生人数下降和.com泡沫的一种反应?我们在纽约大学所观察到的是Java编程课程并未使我们的学生为第一门系统课程做好准备,为更高级的课程所做的就更少了。学生们发现很难编写没有图形界面的程序,对于源程序和事实上硬件怎样工作之间的关系没有感觉,并且(最具破坏性的是)根本不懂得指针的语义,这使得使用C语言进行系统编程非常困难。
我们提倡下列的原则:编程令人着迷的美好存在于把复杂的公式化的流程简化为一小组简单的操作的集合。Java鼓励程序员像硬件商店里的管道工一样求解问题:在大量的抽屉里寻找零件(就是package)组装起来粗略的完成我们想要的东西。怎样做到并不有趣,结果是学生只知道如何把简单的功能组装起来,却不懂得如何编程。更进一步的危害在于,过早地使用Java类库和框架使得学生难以建立运行时开销(run-time cost)的概念,因为很难知道任何的方法调用最后都会被执行。关于这个问题的一份明了的分析参见 [4]。
我们也听到了另外一些反对这种方式(采用Java作为第一门编程语言)的意见。例如Bjarne Stroustrup在德克萨斯州农业大学(Texas A & M University)做的报告中表示:工业界对这种方式也越来越不满。特别地,他提到:
我听到了很多来自工业界的抱怨,尤其是来自AT&T, IBM, Intel, Bloomberg, NI, Microsoft, Lockheed-Martin 。[5]
他提到在一场针对这个问题的私人讨论中,报告如下:
它(德克萨斯州农学院)是这样做的(采用Java作为第一门编程语言),于是我开始向电子工程师教授C++课程,当电子工程系的学生在编程方面超过计算机科学系的学生时,计算机学院转而教授C++语言。[5]
看到如果有很多学院跟随这个趋势将很有趣。在AdaCore,我们也知道很多大学采用Ada作为第一门编程语言,它们也是出于类似的考虑。

真正的程序员能够用任何语言编写程序 (C, Java, Lisp, Ada)

一定年龄的软件专家们会记得一句在两代人之前,结构化编程正风靡一时的时候的口号:真正的程序员能够用任何语言编写Fortran程序。这句口号对于第一门编程语言怎样影响程序员的思考习惯以及如果你只使用一门语言编程将会很难改变这些习惯是一种提醒。反之,我们想要说的一个称职的程序员能够掌握多种不同的语言,并且能够使用每一种语言中的思想工具,即使是在使用其他语言编程的情况下。例如命令式语言(比如Ada以及C++)的使用者应该能够用一种函数式的方式编写程序,这样的能力是通过在Lisp和ML语言中练习操纵递归结构获得的。这是一个能够指出深入学习多种编程语言的重要性的例子。我们认为好的编程语言能够给真正的程序员带来一套思想工具。例如,一个真正的程序员能够在C中实现继承和动态分发,在Lisp中实现信息隐藏,在Ada中实现对树结构进行操作的库,在除Java之外的语言中实现垃圾收集。学习多种不同的语言对于一个技能全面的程序员来说是必须的。

为什么C语言很重要
C语言是每一个人都必须了解的底层语言。它可以被看成一种可移植的汇编语言,而且它暴露了系统底层,从而迫使学生清楚地了解软件和硬件之间的关系。同时,性能分析更直接,因为每一条语句的开销是很清楚的。还有,编译器(比如GCC)提供了查询生成的汇编代码的功能,这对于理解机器语言和架构来说是绝好的工具。

为什么C++很重要
C++带给了C现代软件工程中的许多基本概念:通过类和命名空间进行封装,通过受保护的和私有的数据以及操作进行信息隐藏,通过虚方法和派生类来实现扩展等等。C++也尽可能改进了内存管理,没有使用过度膨胀的垃圾收集而是使用了构造函数和析构函数。

为什么Lisp很重要
每一个程序员都乐于进行函数式编程,并且还有一点不得不提,就是对于引用而言是透明的。虽然如此,多数的程序员还是认为命令式语言是更直觉的,可他们一定会承认在很多场合下一种函数式的没有语句的风格更清晰,自然,易于理解,此外,更有效。
联系使用Lisp语言的一个额外的益处是程序使用一系列的抽象语法(就是内部表示)呈现出来,多数编译器在解析和代码生成阶段使用类似的表示法。所以了解Lisp对于任何涉及语言处理的工作来说都是一种很好的准备。
还有,Lisp是良好的自我定义的。看到一个完整的Lisp语言编写的Lisp解释器是一种智力上的显露,这是每一个计算机科学家都应该经历的。

为什么Java很重要
尽管我们批评使用Java作为第一门语言,我们认为Java在计算机科学教育中依然扮演着重要的角色。我们将提及这个语言的两个方面,它们一定是一个真正的程序员的技术集合中的一部分。
对于并发编程的理解(因为线程提供了一个基本的底层模型)。
反射机制,也就是了解一个程序能够被构造成可以检测它的自身状态并且在动态改变的环境中决定它自己的行为。

为什么Ada很重要
Ada在软件工程中是一门优秀的语言。甚至于当它不再是编程课程中所教授的语言时,它仍然被用来教授软件工程课程。这是因为它拥有强类型封装,信息隐藏,并发,泛型编程,派生等这些概念,体现在一些特定的特性上。通过我们的我们的顾客的经验,我们可以说:一个真正的程序员可以用任何语言编写程序。例如,一个Ada程序员在写C语言程序时习惯使用良好注释的头文件,就像在Ada中编写包时所做的一样。他们会在传递可变结构体时进行边界检查和存在性检查来模仿Ada语言的强类型检查。她会把并发程序组织到任务和受保护的物体中,并且使用良好定义的同步和通信方法。
Ada语言的并发特性在这个多核架构的时代里尤其重要,我们惊奇地发现当这些架构成为软件设计的新的挑战时,Ada在30年以前就已经有良好设计的方法来编写安全,并发的软件。

编程语言并不是全部

一份完整的计算机科学必修课应该包含一门覆盖多种不同类型语言的高级编程课程,能够丰富学生对编程的理解,而不是打造一份证明自己了解热门语言的简历。看到在介绍编程课程时使用脚本语言逐渐流行起来,我们感到有点沮丧。这些语言(Java Script,PHP,Atlas)确实是当今构建Web程序的热门工具,对于教学而言,这些语言也有我们描述Java作为(第一门编程语言)时所具有的全部缺点,并且使得学生没有学习算法和性能分析的机会。缺乏强类型支持倒向了一种易于出错的编程风格,阻碍了学生了解将设计接口和具体实现分离开来的原则。
但是,单独教授正确的语言是不够的,应该向学生展示构建大规模的可靠的程序的工具,像我们在文章开头提到的那样。重要的主题是:学习形式化规范方法,形式化证明方法学,以及获得对高可靠性代码在现实世界中怎样被证实的理解。当你登上一架飞机的时候,你就把你的性命交给了上面的软件,这样的软件还是可靠点比较好。作为一个计算机科学家,你应该懂得如何得到这种级别的可靠性的知识。在当今,对恐怖分子的网络攻击的担忧使得构建无缺陷并且对有害攻击免疫的软件成为当务之急。这种高安全性软件甚至更依赖于形式化方法学,并且我们的学生应该为此做好准备。

引用
1.Joint Taskforce for Computing Curricula. “Computing Curricula 2005: The Overview Report.” ACM/AIS/ IEEE, 2005 <www.acm.org/education /curric_vols/CC2005-March06 Final.pdf>.
2.Barnes, John. High Integrity Ada: The Spark Approach. Addison-Wesley, 2003.
3.Ben-Ari, M. Principles of Concurrent and Distributed Programming. 2nd ed. Addison-Wesley, 2006.
4.Mitchell, Nick, Gary Sevitsky, and Harini Srinivasan. “The Diary of a Datum: An Approach to Analyzing Runtime Complexity in Framework-Based Applications.” Workshop on Library-Centric Software Design, Object-Oriented Programming, Systems, Languages, and Applications, San Diego, CA, 2005.
5.Stroustrup, Bjarne. Private communication. Aug. 2007.
6.Holzmann Gerard J. “The Power of Ten – Rules for Developing Safety Critical Code.” IEEE Computer June 2006: 93-95.
注意
1.Several programming language and system names have evolved from acronyms whose formal spellings are no longer considered applicable to the current names for which they are readily known. ML, Lisp, GCC, PHP, and SPARK fall under this category.

关于作者


Robert B.K. Dewar, Ph.D., is president of AdaCore and a professor emeritus of computer science at New York University. He has been involved in the design and implementation of Ada since 1980 as a distinguished reviewer, a member of the Ada Rapporteur group, and the chief architect of Gnu Ada Translator. He was a member of the Algol68 committee and is the designer and implementor of Spitbol. Dewar lectures widely on programming languages, software methodologies, safety and security, and on intellectual property rights. He has a doctorate in chemistry from the University of Chicago.

AdaCore
104 Fifth AVE 15th FLNew York, NY 10011
Phone: (212) 620-7300 ext. 100
Fax: (212) 807-0162
E-mail: dewar@adacore.com


Edmond Schonberg, Ph.D., is vice-president of AdaCore and a professor emeritus of computer science at New York University. He has been involved in the implementation of Ada since 1981. With Robert Dewar and other collaborators, he created the first validated implementation of Ada83, the first prototype compiler for Ada9X, and the first full implementation of Ada2005. Schonberg has a doctorate in physics from the University of Chicago.

AdaCore
104 Fifth AVE 15th FLNew York, NY 10011
E-mail: schonberg@adacore.com

Revision 2
翻译:yaker gong
校对:zhichao lee(李志超)
E-mail:yakergong at gmail.com
Homepage:http://www.yakergong.com

Categories: programming, translation Tags:

[翻译]插件系统-选择GetProcAddress还是Interfaces

July 18th, 2007 yaker No comments

插件系统-选择GetProcAddress还是Interfaces(译)
原文:
Plugin System – an alternative to GetProcAddress and interfaces
代码下载
[介绍]
有很多文章描述如何实现一个简单的插件框架,比如后面的链接[1]和[2]。通常有两种方法
(1) 插件实现一组标准的(并且通常是小的)函数(方法)。宿主(host)知道这些函数的名字,并且可以通过使用GetProcAddress函数获得这些函数的地址。这并不合适,随着函数数量的增长,维护变得越来越困难。你必须手动通过函数名绑定函数,这样你就不得不做很多工作。
(2)GetProcAddress所返回的函数被用来传递接口指针(Interface Pointer)给插件或者从插件里获取接口指针。剩下的宿主和插件的通信通过接口完成。下面是一个例子

我们将使用一个简单的例子。宿主程序是一个图片查看器。它实现了一个插件框架来增加对不同图片格式的支持(在这个例子中就是24-bit的BMP图象和24 -bit的TGA(Targa)图象)。插件将被实现为DLLs并且将有.imp的扩展名以便和普通dll文件区分开来了.注意,尽管如此,可是这篇文章是关于插件的,而不是关于图象解解析器的。这里所提供的解析器非常基础并且只是用来说明的。

[使用接口的方法]
接口是所有函数都是公共的并且纯虚的基类,并且没有没有数据成员。比如

// IImageParser is the interface that all image parsers
// must implement
class IImageParser
{
public:
    // parses the image file and reads it into a HBITMAP
    virtual HBITMAP ParseFile( const char *fname )=0;
    // returns true if the file type is supported
    virtual bool SupportsType( const char *type ) const=0;
};

实际的图象解析器必须继承自接口类并且实现纯虚函数。BMP文件解析器可能是这个样子。

// CBMPParser implements the IImageParser interface
class CBMPParser: public IImageParser
{
public:
    virtual HBITMAP ParseFile( const char *fname );
    virtual bool SupportsType( const char *type ) const;
 
private:
    HBITMAP CreateBitmap( int width, int height, void **data );
};
 
static CBMPParser g_BMPParser;
// The host calls this function to get access to the
// image parser
extern "C" __declspec(dllexport) IImageParser *GetParser( void )
{
    return &amp;g_BMPParser;
}

宿主将使用LoadLibrary函数来载入BmpParser.imp,然后使用GetProcAddress(”GetParser”)来得到GetParser函数的地址,然后调用它得到IImageParser类的指针。
宿主将保存了注册了的解析器的邻接表(list),它把GetParser函数返回的指针附加到那个邻接表上去。
当宿主需要解析一个bmp文件的时候,它将调用每个解析器的SupportType(”.BMP”)。如果返回类型是true,宿主将调用那个解析器并且使用完整文件名调用待解析文件,并将绘制HBITMAP句柄指向的位图。

基类并不真的必须是纯接口。在技术上这里的限制是所有的成员必须可以通过对象指针访问。所以你可以有:
- 纯虚成员函数(它们能通过虚表被间接访问)
- 数据成员(它们可以通过对象的指针直接访问)
- 内联成员函数(技术上它们不能通过指针访问,但是它们的代码又一次在插件里实例化。
这样就剩下了非内联和静态成员函数。插件无法从宿主访问这样的函数,宿主也不能对插件进行这样的操作。不幸的是在一个大型系统之中,这样的函数要占据代码的大部分。

例如所有的图象解析器需要CreateFunction函数。有必要在基类里声明它并且在宿主端实现。否则每个插件都将有一份这个函数的拷贝。
这个方法的另一个限制是你不能由宿主端暴露任何全局成员或者全局函数给插件端。
我们怎么改进呢?

[把宿主分成Dll和Exe]
让我们看一下USER32模块,它有两个部分 – user32.dll 和 user32.lib。真正的代码和数据在dll中,lib仅仅提供调用dll函数的占位函数。最好的事情在于它是自动发生的。当你链接到user32.lib你就自动地获得访问user32.dll函数的权利。(这里翻译的不好)
MFC 实现得更进一步 – 它暴露你能直接使用和继承的整个类。它们没有我们在上面讨论的纯虚接口类的限制。
我们也能做同样的事情。任何你想提供给插件的函数(我私下觉得原文functionality这个词用得不好)都能放在一个单独的Dll里。使用/IMPLIB 链接器选项来创建相应的 LIB 文件。插件能与那个静态库链接,并且所有导出函数都能提供给它们。你能按你喜欢的方式把代码分成Dll 和 Exe。极限情况下,像在代码里演示的那样,Exe 工程里仅仅含有一行WinMain函数,它仅仅用来启动Dll。
任何你想要导出的全局数据,函数,类,或者成员函数必须被标记为 __declspec(dllexport) 在编译插件时。一个常用的技巧是使用宏

#ifdef COMPILE_HOST
// when the host is compiling
#define HOSTAPI __declspec(dllexport)
#else
// when the plugins are compiling
#define HOSTAPI __declspec(dllimport)
#endif

添加宏COMPILE_HOST的定义到Dll工程里,但是不加到插件工程里。

在宿主Dll端:

// CImageParser is the base class that all image parsers
// must inherit
class CImageParser
{
public:
    // adds the parser to the parsers list
    HOSTAPI CImageParser( void );
    // parses the image file and reads it into a HBITMAP
    virtual HBITMAP ParseFile( const char *fname )=0;
    // returns true if the file type is supported
    virtual bool SupportsType( const char *type ) const=0;
 
protected:
    HOSTAPI HBITMAP CreateBitmap( int width, int height, void **data );
};

现在基类并不仅仅限于一个接口。我们能增加更多基本功能。CreateBitmap函数将被所有解析器共享。
这次不再是宿主调用一个函数来获取解析器并且将它添加到邻接表中,这个功能被CImageParser的构造函数取代。当解析器对象被创建,它的构造函数将自动更新邻接表。宿主不必再使用GetProcAddress函数来看看什么解析器在Dll里。

在插件端:

// CBMPParser inherits from CImageParser
class CBMPParser: public CImageParser
{
public:
    virtual HBITMAP ParseFile( const char *fname );
    virtual bool SupportsType( const char *type ) const;
};
 
static CBMPParser g_BMPParser;

当g_BMPParser被创建是它的构造函数 CBMPParser() 将被调用。那个构造函数(在插件端实现)将调用基类的构造函数CImageParser() (在宿主端实现)。那是可能的因为构造函数被标记为HOSTAPI。
等等,还可以变得更好

[把宿主Dll和Exe连接起来]
(这一部分暂时没翻译)

[链接]
[1] Plug-In framework using DLLs by Mohit Khanna
[2] ATL COM Based Addin / Plugin Framework With Dynamic Toolbars and Menus by thomas_tom99

PS.我想,Interface方法,是在第一种方法之上加入了一个间接层。
我现在到没有太强的感觉非内联函数和静态函数会成为这样一个大型系统的主要部分,Interface的派生类中的非内联函数应该只被纯虚接口函数调用,不然就是接口设计有问题了。

Categories: C++, translation Tags: