在继“是否要把 Filter 扩展集成到 PHP 5.2”这个问题被热闹讨论一番之后,最近 php internals 又开始热烈讨论起了另一个话题:E_DEPRECATED

Marcus Boerger 说在经过了至少三个月的讨论与思考以后,他认为有必要在原来的 E_STRICT 之外再增加一个错误报告级别:E_DEPRECATED。这个 E_DEPRECATED 主要用于在用户使用了一些即将在近期 PHP 版本中被移除的某些语言特性时发出警告。这个“近期的 PHP 版本”具体来说就是与当前版本相隔了两个次(Minor)版本或一个主(major)版本。比如说,假如一个PHP 5.2 中的语言特性将要在 PHP 5.4 或者是 PHP 6.0 中被移除,那么当用户在使用这个语言特性时就应该发出这个 E_DEPRECATED 警告。

与此同时,原来的 E_STRICT 也将只负责其余的像“abstract static”这种不严格符合某些标准但用了也不会对 Zend Engine 造成太大麻烦的情况。而 E_NOTICE 和 E_WARNING 呢,也转为只负责那些输入性的错误(input validations)。

这应该说是一个非常好的建议,基本上所有人都赞成了这个建议。但随后 Marcus Boerger 又抛出了两个比较“激进”的建议:

1、舍弃现在的标准配置文件:php.ini,而改用两个新命名的配置文件:专为开发人员准备的 php-develop.ini (E_ALL|E_STRICT|E_DEPRECATED)和 用于产品部署的 php-production.ini (~(E_DEPRECATED|E_NOTICE|E_WARNING)),并且 E_ALL 也不再包含 E_STRICT or E_DEPRECATED 。

2、应该推迟 PHP 5.2 的发布,直到完成以上所述的变化的为止。

这一下就炸了锅了,各路天神纷纷表达了自己的看法:

Zeev Suraski(就是前两天来咱们这里的那位,我怀疑他是在咱们这里发出这个帖子的^_^)赞成增加 E_DEPRECATED 并支持为此而拖延 PHP 5.2 的发布,但反对再另外增加那两个配置文件。他觉得其实这两个并没有什么不同,而且也不觉得在产品部署时禁止 E_WARNING 和 E_NOTICE (可能还有 E_DEPRECATED)有多么好,万一你要是想同时进行一下日志记录呢?

Ilia Alshanetsky(PHP 5系列的 Release Manager)也同意增加一个E_DEPRECATED ,但他除了不赞成再增加两个配置文件外还反对为此而推迟 PHP 5.2 的发布。并且还为此专门另开一帖专门阐述了他的理由:PHP 5.2 并不是 PHP 5.x 的最后一个版本,没有必要把所有新东西都加在这个版本上。而且会导致目前代码的不稳定。况且目前 PHP 5.2 的发布已经推迟很久了,PHP 5.2 修复了大量的安全性方面的BUG,没有一个用户愿意受到这种攻击,推迟发布就是对所有的PHP用户的一种伤害(disservice)。他建议这些改变应该留到 PHP 6.0 中去完成。

Derick RethansWez Furlong 还有 Pierre 等都发表了各自的意见,但大部分分歧都是集中在是否要为此而推迟 PHP 5.2 的发布上。基本上就是“一步到位” vs “步步为营”。

我个人是支持 Ilia Alshanetsky 的观点的,但目前看起来激进派略占上风。好事多磨,看来 PHP 5.2 至少还得等这件事完全确定下来才会发布。:(

这个版本提供了对 PHP 5.1 的完全支持!在目前所有免费的 PHP 加速器当中,eAccelerator 在性能方面的表现可以说是最优秀的。唯一美中不足的是近来开发较为缓慢,PHP 5.2 将在未来几天内正式发布,而对该版本提供支持的 eAccelerator 0.9.6 却似乎还需要更长一段时间。

但不管如何,PHP 5.1 在性能上也是非常优秀的,此时 eAccelerator 也对其提供完善的支持,升级的时间到了~:D

关于此版本的更多信息请访问 http://www.eaccelerator.net/wiki/Release-0.9.5

这份代码转换自 OllyDbg 的反汇编引擎。我只转换了反汇编器部分,而没有转换汇编器部分。这本来是用于我自用某个程序的一部分,但是后来见到了 pvDasm,觉得 pvDasm 代码结构上的设计更为合理,因此决定采用 pvDasm 的引擎(当然还得转换为 Delphi 代码 :) )。不过使用 Ollydbg 的引擎也有另外一个好处:就是反汇编出来的代码和在 Ollydbg 里面是一样的,我们会很感到很亲切。:D

大家各取所需吧。

OllyDbg Disassembler for Delphi 下载

OK,现在你已经有了一个安全的构建环境,也可以把模块编译进 PHP 了。那么,现在就让我们开始详细讨论一下这里面究竟是如何工作的吧~

模块结构

所有的 PHP 模块通常都包含以下几个部分:

  1. 包含头文件(引入所需要的宏、API定义等);
  2. 声明导出函数(用于 Zend 函数块的声明);
  3. 声明 Zend 函数块;
  4. 声明 Zend 模块;
  5. 实现 get_module() 函数;
  6. 实现导出函数。

包含头文件

模块所必须包含的头文件仅有一个 php.h,它位于 main 目录下。这个文件包含了构建模块时所必需的各种宏和API 定义。

小提示: 专门为模块创建一个含有其特有信息的头文件是一个很好的习惯。这个头文件应该包含 php.h 和所有导出函数的定义。如果你是使用 ext_skel 来创建模块的话,那么你可能已经有了这个文件,因为这个文件会被 ext_skel 自动生成。

声明导出函数

为了声明导出函数(也就是让其成为可以被 PHP 脚本直接调用的原生函数),Zend 提供了一个宏来帮助完成这样一个声明。代码如下:
ZEND_FUNCTION ( my_function );
ZEND_FUNCTION 声明了一个使用 Zend 内部 API 来编译的新的C 函数。这个 C 函数是 void 类型,以 INTERNAL_FUNCTION_PARAMETERS (这是另一个宏)为参数,而且函数名字以 zif_ 为前缀。把上面这句声明展开可以得到这样的代码:
void zif_my_function ( INTERNAL_FUNCTION_PARAMETERS );
接着再把 INTERNAL_FUNCTION_PARAMETERS 展开就会得到这样一个结果:
void zif_my_function(
int ht,
zval * return_value,
zval * this_ptr,
int return_value_used,
zend_executor_globals * executor_globals);

在解释器(interpreter)和执行器(executor)被分离出PHP 包后,这里面(指的是解释器和执行器)原有的一些 API 定义及宏也渐渐演变成了一套新的 API 系统:Zend API。如今的 Zend API 已经承担了很多原来(指的是分离之前)本属于 PHP API 的职责,大量的 PHP API 被以别名的方式简化为对应的 Zend API。我们推荐您应该尽可能地使用 Zend API,PHP API 只是因为兼容性原因才被保留下来。举例来说, zval 和 pval其实是同一类型,只不过 zval 定义在 Zend 部分,而 pval 定义在 PHP 部分(实际上 pval 根本就是 zval 的一个别名)。但由于 INTERNAL_FUNCTION_PARAMETERS 是一个 Zend 宏,因此我们在上面的声明中使用了 zval 。在编写代码时,你也应该总是使用 zval 以遵循新的 Zend API 规范。

这个声明中的参数列表非常重要,你应该牢记于心。(表 3.1 “PHP 调用函数的 Zend 参数”详细介绍了这些参数)

表3.1 PHP 调用函数的 Zend 参数

参数

说明

ht 这个参数包含了Zend 参数的个数。但你不应该直接访问这个值,而是应该通过 ZEND_NUM_ARGS() 宏来获取参数的个数。
return_value 这个参数用来保存函数向 PHP 返回的值。访问这个变量的最佳方式也是用一系列的宏。后面我们会有详细说明。
this_ptr 根据这个参数你可以访问该函数所在的对象(换句话说,此时这个函数应该是一个类的“方法”)。推荐使用函数 getThis() 来得到这个值。
return_value_used 这个值主要用来标识函数的返回值是否为脚本所使用。0 表示脚本不使用其返回值,而 1 则相反。通常用于检验函数是否被正确调用以及速度优化方面,这是因为返回一个值是一种代价很昂贵的操作(可以在 array.c 里面看一下是如何利用这一特性的)。
executor_globals 这个变量指向 Zend Engine 的全局设置,在创建新变量时这个这个值会很有用。我们也可以函数中使用宏 TSRMLS_FETCH() 来引用这个值。

声明 Zend 函数块

现在你已经声明了导出函数,除此之外你还必须得将其引入 Zend 。这些函数的引入是通过一个包含有 N 个zend_function_entry 结构的数组来完成的。数组的每一项都对应于一个外部可见的函数,每一项都包含了某个函数在 PHP 中出现的名字以及在 C 代码中所定义的名字。zend_function_entry 的内部定义如“例3.4 zend_function_entry 的内部声明”所示:

例3.4 zend_function_entry 的内部声明

typedef struct _zend_function_entry {
char *fname;
void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
unsigned char *func_arg_types;
} zend_function_entry;

字段

说明

fname 指定在 PHP 里所见到的函数名(比如:fopen、mysql_connect 或者是我们样例中的 first_module)。
handler 指向对应 C 函数的句柄。样例可以参考前面使用宏INTERNAL_FUNCTION_PARAMETERS 的函数声明。
func_arg_types 用来标识一些参数是否要强制性地按引用方式进行传递。通常应将其设定为 NULL。

对于上面的例子,我们可以这样来声明:
zend_function_entry firstmod_functions[] =
{
ZEND_FE(first_module, NULL)
{NULL, NULL, NULL}
};

你可能已经看到了,这个结构的最后一项是 {NULL, NULL, NULL} 。事实上,这个结构的最后一项也必须始终是 {NULL, NULL, NULL} ,因为 Zend Engine 需要靠它来确认这些导出函数的列表是否列举完毕。

注意:

不应该使用一个预定义的宏来代替列表的结尾部分(即{NULL, NULL, NULL}),因为编译器会尽量寻找一个名为 “NULL” 的函数的指针来代替 NULL !

宏 ZEND_FE(“Zend Function Entry”的简写)将简单地展开为一个zend_function_entry 结构。不过需要注意,这些宏对函数采取了一种很特别的命名机制:把你的C函数前加上一个 zif_ 前缀。比方说,ZEND_FE(first_module) 其实是指向了一个名为 zif_first_module() 的 C 函数。如果你想把宏和一个手工编码的函数名混合使用时(这并不是一个好的习惯),请你务必注意这一点。

小提示: 如果出现了一些引用某个名为 zif_*() 函数的编译错误,那十有八九与 ZEND_FE 所定义的函数有关。

“表 3.2 可用来定义函数的宏”给出了一个可以用来定义一个函数的所有宏的列表:

表3.2 可用来定义函数的宏

说明

ZEND_FE(name, arg_types) 定义了一个zend_function_entry 内字段name为 “name” 的函数。arg_types 应该被设定为 NULL。这个声明需要有一个对应的 C 函数,该这个函数的名称将自动以 zif_ 为前缀。举例来说, ZEND_FE(”first_module”, NULL) 就引入了一个名为 first_module() 的 PHP 函数,并被关联到一个名为 zif_first_module() 的C函数。这个声明通常与 ZEND_FUNCTION 搭配使用。
ZEND_NAMED_FE(php_name, name, arg_types) 定义了一个名为 php_name 的 PHP 函数,并且被关联到一个名为 name 的 C 函数。arg_types 应该被设定为 NULL。 如果你不想使用宏 ZEND_FE 自动创建带有 zif_ 前缀的函数名的话可以用这个来代替。通常与 ZEND_NAMED_FUNCTION搭配使用。
ZEND_FALIAS(name, alias, arg_types) 为 name 创建一个名为 alias 的别名。arg_types 应该被设定为 NULL。这个声明不需要有一个对应的 C 函数,因为它仅仅是创建了一个用来代替 name 的别名而已。
PHP_FE(name, arg_types) 以前的 PHP API,等同于 ZEND_FE 。仅为兼容性而保留,请尽量避免使用。
PHP_NAMED_FE(runtime_name, name, arg_types) 以前的 PHP API,等同于ZEND_NAMED_FE 。仅为兼容性而保留,请尽量避免使用。

注意:你不能将 ZEND_FE 和 PHP_FUNCTION 混合使用,也不能将PHP_FE 和 ZEND_FUNCTION 混合使用。但是将 ZEND_FE + ZEND_FUNCTION 和 PHP_FE + PHP_FUNCTION 一起混合使用是没有任何问题的。当然我们并不推荐这样的混合使用,而是建议你全部使用 ZEND_* 系列的宏。

声明 Zend 模块

Zend 模块的信息被保存在一个名为zend_module_entry 的结构,它包含了所有需要向 Zend 提供的模块信息。你可以在“例 3.5 zend_module_entry 的内部声明”中看到这个 Zend 模块的内部定义:

例3.5 zend_module_entry 的内部声明

typedef struct _zend_module_entry zend_module_entry;

struct _zend_module_entry {
unsigned short size;
int zend_api;
unsigned char zend_debug;
unsigned char zts;
char *name;
zend_function_entry *functions;
int (*module_startup_func)(INIT_FUNC_ARGS);
int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
int (*request_startup_func)(INIT_FUNC_ARGS);
int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);
char *version;

// 其余的一些我们不感兴趣的信息
};

字段 说明
size, zend_api, zend_debug and zts 通常用 “STANDARD_MODULE_HEADER” 来填充,它指定了模块的四个成员:标识整个模块结构大小的 size ,值为 ZEND_MODULE_API_NO 常量的 zend_api,标识是否为调试版本(使用 ZEND_DEBUG 进行编译)的 zend_debug,还有一个用来标识是否启用了 ZTS (Zend 线程安全,使用 ZTS 或 USING_ZTS 进行编译)的 zts。
name 模块名称 (像“File functions”、“Socket functions”、“Crypt”等等). 这个名字就是使用 phpinfo() 函数后在“Additional Modules”部分所显示的名称。
functions Zend 函数块的指针, 这个我们在前面已经讨论过。
module_startup_func 模块启动函数。这个函数仅在模块初始化时被调用,通常用于一些与整个模块相关初始化的工作(比如申请初始化的内存等等)。如果想表明模块函数调用失败或请求初始化失败请返回 FAILURE,否则请返回 SUCCESS。可以通过宏 ZEND_MINIT 来声明一个模块启动函数。如果不想使用,请将其设定为 NULL。
module_shutdown_func 模块关闭函数。这个函数仅在模块卸载时被调用,通常用于一些与模块相关的反初始化的工作(比如释放已申请的内存等等)。这个函数和 module_startup_func() 相对应。如果想表明函数调用失败或请求初始化失败请返回 FAILURE,否则请返回 SUCCESS。可以通过宏ZEND_MSHUTDOWN 来声明一个模块关闭函数。如果不想使用,请将其设定为 NULL。
request_startup_func 请求启动函数。这个函数在每次有页面的请求时被调用,通常用于与该请求相关的的初始化工作。如果想表明函数调用失败或请求初始化失败请返回 FAILURE,否则请返回 SUCCESS。注意: 如果该模块是在一个页面请求中被动态加载的,那么这个模块的请求启动函数将晚于模块启动函数的调用(其实这两个初始化事件是同时发生的)。可以使用宏 ZEND_RINIT 来声明一个请求启动函数,若不想使用,请将其设定为 NULL。
request_shutdown_func 请求关闭函数。这个函数在每次页面请求处理完毕后被调用,正好与 request_startup_func() 相对应。如果想表明函数调用失败或请求初始化失败请返回 FAILURE,否则请返回 SUCCESS。注意: 当在页面请求作为动态模块加载时, 这个请求关闭函数先于模块关闭函数的调用(其实这两个反初始化事件是同时发生的)。可以使用宏 ZEND_RSHUTDOWN 来声明这个函数,若不想使用,请将其设定为 NULL 。
info_func 模块信息函数。当脚本调用 phpinfo() 函数时,Zend 便会遍历所有已加载的模块,并调用它们的这个函数。每个模块都有机会输出自己的信息。通常情况下这个函数被用来显示一些环境变量或静态信息。可以使用宏 ZEND_MINFO 来声明这个函数,若不想使用,请将其设定为 NULL 。
version 模块的版本号。如果你暂时还不想给某块设置一个版本号的话,你可以将其设定为 NO_VERSION_YET。但我们还是推荐您在此添加一个字符串作为其版本号。版本号通常是类似这样: “2.5-dev”, “2.5RC1”, “2.5” 或者 “2.5pl3” 等等。
Remaining structure elements 这些字段通常是在模块内部使用的,通常使用宏STANDARD_MODULE_PROPERTIES 来填充。而且你也不应该将他们设定别的值。STANDARD_MODULE_PROPERTIES_EX 通常只会在你使用了全局启动函数(ZEND_GINIT)和全局关闭函数(ZEND_GSHUTDOWN)时才用到,一般情况请直接使用 STANDARD_MODULE_PROPERTIES 。

在我们的例子当中,这个结构被定义如下:
zend_module_entry firstmod_module_entry =
{
STANDARD_MODULE_HEADER,
“First Module”,
firstmod_functions,
NULL, NULL, NULL, NULL, NULL,
NO_VERSION_YET,
STANDARD_MODULE_PROPERTIES,
};

这基本上是你可以设定最简单、最小的一组值。该模块名称为“First Module”,然后是所引用的函数列表,其后所有的启动和关闭函数都没有使用,均被设定为了 NULL。

作为参考,你可以在表 3.3 “所有可声明模块启动和关闭函数的宏”中找到所有的可设置启动与关闭函数的宏。这些宏暂时在我们的例子中还尚未用到,但稍后我们将会示范其用法。你应该使用这些宏来声明启动和关闭函数,因为它们都需要引入一些特殊的变量( INIT_FUNC_ARGS 和 SHUTDOWN_FUNC_ARGS ),而这两个参数宏将在你使用下面这些预定义宏时被自动引入(其实就是图个方便)。如果你是手工声明的函数或是对函数的参数列表作了一些必要的修改,那么你就应该修改你的模块相应的源代码来保持兼容。

表3.3 所有可声明模块启动和关闭函数的宏

描述

ZEND_MINIT(module) 声明一个模块的启动函数。函数名被自动设定为zend_minit_<module> (比如:zend_minit_first_module)。通常与ZEND_MINIT_FUNCTION 搭配使用。
ZEND_MSHUTDOWN(module) 声明一个模块的关闭函数。函数名被自动设定为zend_mshutdown_<module> (比如:zend_mshutdown_first_module)。通常与ZEND_MSHUTDOWN_FUNCTION搭配使用。
ZEND_RINIT(module) 声明一个请求的启动函数。函数名被自动设定为zend_rinit_<module> (比如:zend_rinit_first_module)。通常与ZEND_RINIT_FUNCTION搭配使用。
ZEND_RSHUTDOWN(module) 声明一个请求的关闭函数。函数名被自动设定为zend_rshutdown_<module> (比如:zend_rshutdown_first_module)。通常与ZEND_RSHUTDOWN_FUNCTION 搭配使用。
ZEND_MINFO(module) 声明一个输出模块信息的函数,用于 phpinfo()。函数名被自动设定为zend_info_<module> (比如:zend_info_first_module)。通常与 ZEND_MINFO_FUNCTION 搭配使用。

实现 get_module() 函数

这个函数只用于动态可加载模块。我们先来看一下如何通过宏 ZEND_GET_MODULE 来创建这个函数:
#if COMPILE_DL_FIRSTMOD
ZEND_GET_MODULE(firstmod)
#endif

这个函数的实现被一条件编译语句所包围。这是很有必要的,因为 get_module() 函数仅仅在你的模块想要编译成动态模块时才会被调用。通过在编译命令行指定编译条件:COMPILE_DL_FIRSTMOD (也就是上面我们设置的那个预定义)的打开与否,你就可以决定是编译成一个动态模块还是编译成一个内建模块。如果想要编译成内建模块的话,那么这个 get_module() 将被移除。

get_module() 函数在模块加载时被 Zend 所调用,你也可以认为是被你 PHP 脚本中的 dl() 函数所调用。这个函数的作用就是把模块的信息信息块传递 Zend 并通知 Zend 获取这个模块的相关内容。

如果你没有在一个动态可加载模块中实现 get_module() 函数,那么当你在访问它的时候 Zend 就会向你抛出一个错误信息。

实现导出函数

导出函数的实现是我们构建扩展的最后一步。在我们的 first_module 例子中,函数被实现如下:
ZEND_FUNCTION(first_module)
{
long parameter;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &parameter) == FAILURE) {
return;
}
RETURN_LONG(parameter) ;
}

这个函数是用宏 ZEND_FUNCTION 来声明的,和前面我们讨论的 Zend 函数块中的 ZEND_FE 声明相对应。在函数的声明之后,我们的代码便开始检查和接收这个函数的参数。在将参数进行转换后将其值返回。(参数的接收和处理我们马上会在下一节中讲到)。

小结

一切基本上就这样了 ―― 我们在实现一个模块时不会再遇到其他方面的事了。内建模块也基本上同动态模块差不多。因此,有了前面几节我们所掌握的信息,再在你遇到 PHP 源代码的时候你就有能力去搞定这些小麻烦。

在下面的几个小节里,我们将会学习到如何利用 PHP 内核来创建一个更为强大的扩展!

一直在关注 php.internals 邮件列表,也很关注 PHP 5.2 的动态。PHP 5.2 是一个非常值得关注和升级的版本,增加了许多很实用的特性,也对运行效率进行了很大的改善。

众所周知,对不安全的数据(比如来自客户端的请求等)进行过滤和处理是每个 PHP 程序员的必修课,也是每个 PHP 项目中不可或缺的一个过程。基于安全性考虑,几乎每个来自客户端的请求都必须经过该过程处理。长时间以来我们都是在“脚本级”代码中处理这些请求,如果能在编译后的二进制代码中完成这些处理那自然是再好不过了。一则可以减少代码量、增加清晰度和改善可读性,二则更可以大幅提高程序性能。PHP 5.2 新增的 Filter 扩展正是为此应运而生。

不过好事自然要“多磨”。原定于 9 月 28 日发布的 PHP 5.2 由于 Filter 扩展的 API 兼容性问题被迫延迟发布。所谓“API 兼容性”问题说的是 Filter 扩展的两个 Leader:DerickPierre 分别为这个扩展设计了两个不同“风格”的API,这两套 API 都似乎很有道理但却似乎毫不相容。PHP 5.2 预定发布在即,然而问题却没有丝毫可以解决的迹象,以致于作为 Release Manager 的 Ilia 专门设立了一个投票来决定是否要暂时把 Filter 扩展从源码树中移除来确保 PHP 5.2 的按时发布。最终很幸运,经过两个主角的协商,决定以 Pierre 的代码为基础并作了一些修订,这个问题基本获得了圆满解决

开发小组已经决定在今天发布一个 RC5 版本作为正式版的缓冲和热身,略做一些修订和调整后,PHP 5.2 将会在大约一周后正式发布!热烈期待ing……:D

实际上,在对静态或动态模块进行编译时没有太多故障处理工作要做。唯一可能的问题就是编译器会警告说找不到某些定义或者类似的事情。如果出现这种情况,你应该确认一下所有的头文件都是可用的并且它们的路径都已经在编译命令中被指定。为了确保每个文件都能被正确地定位,你可以先提取一个干净的 PHP 源码树,然后在 Ext 目录使用自动构建工具来创建这些文件。用这种方法就可以确保一个安全的编译环境。假如这样也不行,那就只好试试手动编译了。

PHP 也可能会警告说在你的模块里面有一些未定义的函数。(如果你没有改动样例文件的话这种情况应该不会发生。)假如你在模块中拼错了一些你想访问的外部函数的名字,那么它们就会在符号表中显示为“未能连接的符号”。这样在 PHP 动态加载或连接时,它们就不会运行--在二进制文件中没有相应的符号。为了解决这个问题,你可以在你的模块文件中找一下错误的声明或外部引用。注意,这个问题仅仅发生在动态可加载模块身上。而在静态模块身上则不会发生,因为静态模块在编译时就会抛出这些错误。

根据你所选择的不同的构建过程,你要么把扩展编译进一个新的PHP 的二进制文件,然后再连接到 Web 服务器(或以CGI 模式运行),要么将其编译成一个 .so (共享库)文件。如果你将上面的样例文件 first_module.c 编译成了一个共享库,那么编译后的文件应该是 first_module.so。要想使用它,你就必须把他复制到一个 PHP 能访问到的地方。如果仅仅是为了测试的话,简单起见,你可以把它复制到你的 htdocs 目录下,然后用“例3.3 first_module.so 的一个测试文件”中的代码来进行一下测试。如果你将其直接编译编译进 PHP 二进制文件的话,那就不用调用 dl() 函数了,因为这个模块的函数在脚本一开始运行就生效了。

警告:

为了安全起见,你不应该将你的动态模块放入一个公共目录。即使是一个简单的测试你可以那么做,那也应该把它放进产品环境中的一个隔离的目录。

例3.3 first_module.so 的一个测试文件


<?php
// remove next comment if necessary
// dl("first_module.so");
$param = 2
$return = first_module($param);
print("We sent '$param' and got '$return'");
?>

调用这个测试文件,结果应该输出为:We sent ‘2′ and got ‘2′。

若有需要,你可以调用 dl() 函数来载入一个动态可加载模块。这个函数负责寻找指定的共享库并进行加载使其函数在 PHP 中生效。这个样例模块仅输出了一个函数 first_module(),这个函数仅接受一个参数,并将其转换为整数作为函数的结果返回。

如果你已经进行到了这一步,那么,恭喜你,你已经成功创建了你的第一个 PHP 扩展!

在今天凌晨,Turbo Explorer 发布倒计时牌终于置零。随后,Borland 也放出了下载链接。一路小跑,安装过了,开始菜单程序组的名字还是叫作 Borland Developer Studio 2006。安装时必须先卸载 BDS 2006 才能继续安装 Turbo Delphi Explorer。安装的默认目录依旧是 C:\Program Files\Borland\BDS\4.0\,内部版本号为 10.0.2288.42451 Update 2。对比了一下 BDS 2006 和 Turbo Delphi 的特性列表,几乎毫无差别。看来仍然是换汤不换药,新瓶装老酒啊。甚至可以说连汤和瓶都没换,只是换了个标签。这个 Borland 啊~

作为 PHP 6 重大更新之一的 APC 现在已经从 PHP Internal 部分独立出来有了属于自己邮件列表了:APC development,APC 的主要开发者,PHP 的创始人 Rasmus Lerdorf 当仁不让地主持了工作,PHP Core方面的不少牛人也将在那里出没。关心这方面工作地朋友一定要订阅哦~

我们先来创建一个非常简单的扩展,这个扩展除了一个将其整形参数作为返回值的函数外几乎什么都没有。下面(“例3-2 一个简单的扩展”)就是这个样例的代码:

例3.2 一个简单的扩展


/* include standard header */
#include "php.h"

/* declaration of functions to be exported */
ZEND_FUNCTION(first_module);

/* compiled function list so Zend knows what‘s in this module */
zend_function_entry firstmod_functions[] =
{
ZEND_FE(first_module, NULL)
{NULL, NULL, NULL}
};

/* compiled module information */
zend_module_entry firstmod_module_entry =
{
STANDARD_MODULE_HEADER,
“First Module”,
firstmod_functions,
NULL,
NULL,
NULL,
NULL,
NULL,
NO_VERSION_YET,
STANDARD_MODULE_PROPERTIES
};

/* implement standard "stub" routine to introduce ourselves to Zend */
#if COMPILE_DL_FIRST_MODULE
ZEND_GET_MODULE(firstmod)
#endif

/* implement function that is meant to be made available to PHP */
ZEND_FUNCTION(first_module)
{
long parameter;

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &parameter) == FAILURE) {
return;
}

RETURN_LONG(parameter);
}

这段代码已经包含了一个完整的 PHP 模块。稍后我们会详细解释这段代码,现在让我们先讨论一下构建过程。(在我们讨论 API 函数前,这可以让心急的人先实验一下。)

模块的编译

模块的编译基本上有两种方法:

1、在 Ext 目录内使用“make” 机制,这种机制也可以编译出动态可加载模块。
2、手动编译源代码。

第一种方法明显受到人们的偏爱。自 PHP 4.0 以来,这也被标准化成了一个的复杂的构建过程。这种复杂性也导致了难于被理解这个缺点。在本章最后我们会更详细的讨论这些内容,但现在还是让我们使用默认的 make 文件吧。

第二种方法很适合那些(因为某种原因而)没有完整 PHP 源码树的或者是很喜欢敲键盘的人。虽然这些情况是比较罕见,但为了内容的完整性我们也会介绍一下这种方法。

使用 make 进行编译

为了能够使用这种标准机制流程来编译这些代码,让我们把它所有的子目录都复制到 PHP 源码树的 Ext 目录下。然后运行 buildconf 命令,这将会创建一个新的包含了与我们的扩展相对应的选项的 configure 脚本。默认情况下,样例中的所有代码都是未激活的,因此你不用担心会破坏你的构建程序。在 buildconf 执行完毕后,再使用 configure –help 命令就会显示出下面的附加模块:

--enable-array_experiments BOOK: Enables array experiments
--enable-call_userland BOOK: Enables userland module
--enable-cross_conversion BOOK: Enables cross-conversion module
--enable-first_module BOOK: Enables first module
--enable-infoprint BOOK: Enables infoprint module
--enable-reference_test BOOK: Enables reference test module
--enable-resource_test BOOK: Enables resource test module
--enable-variable_creation BOOK: Enables variable-creation module

前面样例(“例3-2 一个简单的扩展”)中的模块(first_module)可以使用 –enable-first_module 或 –enable-first_module=yes 来激活。

手动编译

手动编译需要运行以下命令:

动作 命令
编译 cc -fpic -DCOMPILE_DL_FIRST_MODULE=1 -I/usr/local/include -I. -I.. -I../Zend -c -o <your_object_file> <your_c_file>
连接 cc -shared -L/usr/local/lib -rdynamic -o <your_module_file> <your_object_file(s)>

编译命令只是简单的让编译器产生一些中间代码(不要忽略了-fpic 参数),然后又定义了COMPILE_DL 常量来通知代码这是要编译为一个动态可加载的模块(通常用来测试,我们稍后会讨论它)。这些选项后面是一些编译这些源代码所必须包含的库文件目录。

注意:本例中所有 include 的路径都是都是 Ext 目录的相对路径。如果您是在其他目录编译的这些源文件,那么还要相应的修改路径名。编译所需要的目录有 PHP 目录,Zend 目录和模块所在的目录(如果有必要的话)。

连接命令也是一个非常简单的把模块连接成一个动态模块的命令。

你可以在编译指令中加入优化选项,尽管这些已经在样例中忽略了(不过你还是可以从前面讨论的 make 模版文件中发现一些)。

注意,手动将模块静态编译和连接到 PHP 二进制代码的指令很长很长,因此我们在这里不作讨论。(手动输入那些指令是很低效的。)

« Previous PageNext Page »