Zend API:深入 PHP 内核 (-)译序及进度目录(已翻译完毕) 译序:网上关于PHP的资料多如牛毛,关于其核心ZendEngine的却少之又少。PHP中文手册出现已N年,但ZendAPI的翻译却仍然不见动静,小弟自觉对ZendEngine略有小窥,并且翻译也有助于强迫自己对文章的进一步理解,于是尝试翻译此章,英文不好,恭请方家指点校核。转载请注明来自抚琴居(译者主页):中文手册《ZendAPI:深入PHP内核》一章当前翻译进度(已翻译完毕):1.摘要2.概述3.可扩展性4.源码布局5.自动构建系统6.开始创建扩展7.使用扩展8.故障处理9.关于模块代码的讨论10.接收参数11.创建变量12.使用拷贝构造函数复制变量内容13.返回函数值14.信息输出15.启动函数与关闭函数16.调用用户函数17.支持初始化文件(php.ini)18.何去何从19.参考:关于配置文件的一些宏20.API宏Zend API:深入 PHP 内核 (二)摘要 摘要知者不言,言者不知。――老子《道德经》五十六章有时候,单纯依靠PHP“本身”是不行的。尽管普通用户很少遇到这种情况,但一些专业性的应用则经常需要将PHP的性能发挥到极致(这里的性能是指速度或功能)。由于受到PHP语言本身的限制,同时还可能不得不把庞大的库文件包含到每个脚本当中。因此,某些新功能并不是总能被顺利实现,所以我们必须另外寻找一些方法来克服PHP的这些缺点。了解到了这一点,我们就应该接触一下PHP的心脏并探究一下它的内核-可以编译成PHP并让之工作的C代码-的时候了。Zend API:深入 PHP 内核 (三)概述 “扩展PHP”说起来容易做起来难。PHP现在已经发展成了一个具有数兆字节源代码的非常成熟的系统。要想深入这样的一个系统,有很多东西需要学习和考虑。在写这一章节的时候,我们昀终决定采用“边学边做”的方式。这也许并不是昀科学和专业的方式,但却应该是昀有趣和昀有效的一种方式。在下面的小节里,你首先会非常快速的学习到如何写一个虽然很基础但却能立即运行的扩展,然后将会学习到有关ZendAPI的高级功能。另外一个选择就是将其作为一个整体,一次性的讲述所有的这些操作、设计、技巧和诀窍等,并且可以让我们在实际动手前就可以得到一副完整的愿景。这看起来似乎是一个更好的方法,也没有死角,但它却枯燥无味、费时费力,很容易让人感到气馁。这就是我们为什么要采用非常直接的讲法的原因。注意,尽管这一章会尽可能多讲述一些关于PHP内部工作机制的知识,但要想真的给出一份在任何时间任何情况下的PHP扩展指南,那简直是不可能的。PHP是如此庞大和复杂,以致于只有你亲自动手实践一下才有可能真正理解它的内部工作机制,因此我们强烈推荐你随时参考它的源代码来进行工作。Zend是什么?PHP又是什么?Zend指的是语言引擎,PHP指的是我们从外面看到的一套完整的系统。这听起来有点糊涂,但其实并不复杂(见图3-1PHP内部结构图)。为了实现一个WEB脚本的解释器,你需要完成以下三个部分的工作:1.解释器部分:负责对输入代码的分析、翻译和执行;2.功能性部分:负责具体实现语言的各种功能(比如它的函数等等);3.接口部分:负责同WEB服务器的会话等功能。Zend包括了第一部分的全部和第二部分的局部,PHP包括了第二部分的局部和第三部分的全部。他们合起来称之为PHP包。Zend构成了语言的核心,同时也包含了一些昀基本的PHP预定义函数的实现。PHP则包含了所有创造出语言本身各种显著特性的模块。图3-1PHP内部结构图下面将要讨论PHP允许在哪里扩展以及如何扩展。Zend API:深入 PHP 内核 (四)可扩展性 正如上图(图3-1PHP内部结构图)所示,PHP主要以三种方式来进行扩展:外部模块,内建模块和Zend引擎。下面我们将分别讨论这些方式:外部模块外部模块可以在脚本运行时使用dl()函数载入。这个函数从磁盘载入一个共享对象并将它的功能与调用该函数的脚本进行绑定并使之生效。脚本终止后,这个外部模块将在内存中被丢弃。这种方式有利有弊,如下表所示:优点缺点外部模块不需要重新对PHP进行编译。共享对象在每次脚本调用时都需要对其进行加载,速度较慢。PHP通过“外包”方式来让自身的体积保持很小。附加的外部模块文件会让磁盘变得比较散乱。每个想使用该模块功能的脚本都必须使用dl()函数手动加载,或者在php.ini文件当中添加一些扩展标签(这并不总是一个恰当的解决方案)。综上所述,外部模块非常适合开发第三方产品,较少使用的附加的小功能或者仅仅是调试等这些用途。为了迅速开发一些附加功能,外部模块是昀佳方式。但对于一些经常使用的、实现较大的,代码较为复杂的应用,那就有些得不偿失了。第三方可能会考虑在php.ini文件中使用扩展标签来创建一个新的外部模块。这些外部模块完全同主PHP包分离,这一点非常适合应用于一些商业环境。商业性的发行商可以仅发送这些外部模块而不必再额外创建那些并不允许绑定这些商业模块的PHP二进制代码。内建模块内建模块被直接编译进PHP并存在于每一个PHP处理请求当中。它们的功能在脚本开始运行时立即生效。和外部模块一样,内建模块也有一下利弊:优点缺点无需专门手动载入,功能即时生效。修改内建模块时需要重新编译PHP。无需额外的磁盘文件,所有功能均内置在PHP二进制代码当中。PHP二进制文件会变大并且会消耗更多的内存。Zend引擎当然,你也能直接在Zend引擎里面进行扩展。如果你需要在语言特性方面做些改动或者是需要在语言核心内置一些特别的功能,那么这就是一种很好的方式。但一般情况下应该尽力避免对Zend引擎的修改。这里面的改动会导致和其他代码的不兼容,而且几乎没有人会适应打过特殊补丁的Zend引擎。况且这些改动与主PHP源代码是不可分割的,因此就有可能在下一次的官方的源代码更新中被覆盖掉。因此,这种方式通常被认为是“不良的习惯”。由于使用极其稀少,本章将不再对此进行赘述。Zend API:深入 PHP 内核 (五)源码布局 在我们开始讨论具体编码这个话题前,你应该让自己熟悉一下PHP的源代码树以便可以迅速地对各个源文件进行定位。这也是编写和调试PHP扩展所必须具备的一种能力。下表列出了一些主要目录的内容:目录内容php-src包含了PHP主源文件和主头文件;在这里你可以找到所有的PHPAPI定义、宏等内容。(重要).其他的一些东西你也可以在这里找到。php-src/ext这里是存放动态和内建模块的仓库;默认情况下,这些就是被集成于主源码树中的“官方”PHP模块。自PHP4.0开始,这些PHP标准扩展都可以编译为动态可载入的模块。(至少这些是可以的)。php-src/main这个目录包含主要的PHP宏和定义。(重要)php-src/pear这个目录就是“PHP扩展与应用仓库”的目录。包含了PEAR的核心文件。php-src/sapi包含了不同服务器抽象层的代码。TSRMZend和PHP的“线程安全资源管理器”(TSRM)目录。ZendEngine2包含了Zend引擎文件;在这里你可以找到所有的ZendAPI定义与宏等。(重要)当然,讨论PHP包里面全部每一个文件无疑是超出了本章的范围,但你还是应该仔细看一下下面的几个文件•php-src/main/php.h,位于PHP主目录。这个文件包含了绝大部分PHP宏及API定义。•php-src/Zend/zend.h,位于Zend主目录。这个文件包含了绝大部分Zend宏及API定义。•php-src/Zend/zend_API.h,也位于Zend主目录,包含了ZendAPI的定义。除此之外,你也应该注意一下这些文件所包含的一些文件。举例来说,哪些文件与Zend执行器有关,哪些文件又为PHP初始化工作提供了支持等等。在阅读完这些文件之后,你还可以花点时间再围绕PHP包来看一些文件,了解一下这些文件和模块之间的依赖性――它们之间是如何依赖于别的文件又是如何为其他文件提供支持的。同时这也可以帮助你适应一下PHP创作者们代码的风格。要想扩展PHP,你应该尽快适应这种风格。扩展规范Zend是用一些特定的规范构建的。为了避免破坏这些规范,你应该遵循以下的几个规则:宏几乎对于每一项重要的任务,Zend都预先提供了极为方便的宏。在下面章节的图表里将会描述到大部分基本函数、结构和宏。这些宏定义大多可以在Zend.h和Zend_API.h中找到。我们建议您在学习完本节之后仔细看一下这些文件。(当然你也可以现在就阅读这些文件,但你可能不会留下太多的印象。)内存管理资源管理仍然是一个极为关键的问题,尤其是对服务器软件而言。资源里昀具宝贵的则非内存莫属了,内存管理也必须极端小心。内存管理在Zend中已经被部分抽象,而且你也应该坚持使用这些抽象,原因显而易见:由于得以抽象,Zend就可以完全控制内存的分配。Zend可以确定一块内存是否在使用,也可以自动释放未使用和失去引用的内存块,因此就可以避免内存泄漏。下表列出了一些常用函数:函数描述emalloc()用于替代malloc()。efree()用于替代free()。estrdup()用于替代strdup()。estrndup()用于替代strndup()。速度要快于estrdup()而且是二进制安全的。如果你在复制之前预先知道这个字符串的长度那就推荐你使用这个函数。ecalloc()用于替代calloc()。erealloc()用于替代realloc()。emalloc(),estrdup(),estrndup(),ecalloc(),和erealloc()用于申请内部的内存,efree()则用来释放这些前面这些函数申请的内存。e*()函数所用到的内存仅对当前本地的处理请求有效,并且会在脚本执行完毕,处理请求终止时被释放。Zend还有一个线程安全资源管理器,这可以为多线程WEB服务器提供更好的本地支持。不过这需要你为所有的全局变量申请一个局部结构来支持并发线程。但是因为在写本章内容时Zend的线程安全模式仍未完成,因此我们无法过多地涉及这个话题。目录与文件函数下列目录与文件函数应该在Zend模块内使用。它们的表现和对应的C语言版本完全一致,只是在线程级提供了虚拟目录的支持。Zend函数对应的C函数V_GETCWD()getcwd()V_FOPEN()fopen()V_OPEN()open()V_CHDIR()chdir()V_GETWD()getwd()V_CHDIR_FILE()将当前的工作目录切换到一个以文件名为参数的该文件所在的目录。V_STAT()stat()V_LSTAT()lstat()字符串处理在Zend引擎中,与处理诸如整数、布尔值等这些无需为其保存的值而额外申请内存的简单类型不同,如果你想从一个函数返回一个字符串,或往符号表新建一个字符串变量,或做其他类似的事情,那你就必须确认是否已经使用上面的e*()等函数为这些字符串申请内存。(你可能对此没有多大的感觉。无所谓,现在你只需在脑子里有点印象即可,我们稍后就会再次回到这个话题)复杂类型像数组和对象等这些复杂类型需要另外不同的处理。它们被出存在哈希表中,Zend提供了一些简单的API来操作这些类型。Zend API:深入 PHP 内核 (六)自动构建系统 PHP提供了一套非常灵活的自动构建系统(automaticbuildsystem),它把所有的模块均放在Ext子目录下。每个模块除自身的源代码外,还都有一个用来配置该扩展的config.m4文件(详情请参见)。包括.cvsignore在内的所有文件都是由位于Ext目录下的ext_skel脚本自动生成的,它的参数就是你想创建模块的名称。这个脚本会创建一个与模块名相同的目录,里面包含了与该模块对应的一些的文件。下面是操作步骤::~/cvs/php4/ext:./ext_skel--e