标签:php扩展 处理 动态加载 source ble when width 存在 other
这个是继:使用ext_skel和phpize构建php5扩展 内容 (拆分出来) Zend_API:深入_PHP_内核:http://cn2.php.net/manual/zh/internals2.ze1.php
我们使用ext_skel创建扩展 hello_module,该模块包含一个方法:hello_world。
使用ext_skel 生成的代码都是PHP_开头的宏, 而不是ZEND_开头. 实际上这两者是一样的。
在源代码src/main/PHP.h 中发现: PHP_FUNCTION 就是ZEND_FUNCTION的别名,即:
其实,很多”PHP_”开头的宏都用对应的”ZEND_”开头的宏,这个应该是为兼容之前的版本吧。
php_hello_module.h
hello_module.c
所有的 PHP模块通常都包含以下几个部分:
· 包含头文件(引入所需要的宏、API定义等);
· 声明导出函数(用于 Zend函数块的声明);
· 声明 Zend函数块;
· 声明 Zend模块;
· 实现get_module()函数;
· 实现导出函数。
模块所必须包含的头文件仅有一个 php.h,它位于 main目录下。这个文件包含了构建模块时所必需的各种宏和API定义。
小提示:专门为模块创建一个含有其特有信息的头文件是一个很好的习惯。这个头文件应该包含 php.h和所有导出函数的定义。如果你是使用 ext_skel来创建模块的话,那么你可能已经有了这个文件,因为这个文件会被 ext_skel自动生成。
为了声明导出函数(也就是让其成为可以被 PHP脚本直接调用的原生函数),Zend提供了一个宏来帮助完成这样一个声明。代码如下:
ZEND_FUNCTION 宏声明了一个使用 Zend内部 API来编译的新的C函数。这个 C函数是 void类型,以 INTERNAL_FUNCTION_PARAMETERS(这是另一个宏)为参数,而且函数名字以 zif_为前缀。把上面这句声明展开可以得到这样的代码:
接着再把 INTERNAL_FUNCTION_PARAMETERS展开就会得到这样一个结果, 即ZEND_FUNCTION最终扩展结果:
可见,ZEND_FUNCTION就是简单的声明了一个名为zif_ first_module的C函数,zif可能是”Zend Internal Function”的缩写。
在解释器(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规范。
这个声明中的参数列表非常重要,你应该牢记于心。
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的内部声明:
字段 |
说明 |
fname |
指定在 PHP里所见到的函数名(比如:fopen、mysql_connect或者是我们样例中的hello_world)。 |
handler |
指向对应 C函数的句柄。样例可以参考前面使用宏INTERNAL_FUNCTION_PARAMETERS的函数声明。 |
func_arg_types |
用来标识一些参数是否要强制性地按引用方式进行传递。通常应将其设定为 NULL。 |
对于我们要创建的模块:hello_module.该模块提供一个方法:hello_word,可以这样声明:
这样完成C函数到PHP函数的映射的语句。
你可能已经看到了,这个结构的最后一项是 {NULL, NULL, NULL}。事实上,这个结构的最后一项也必须始终是 {NULL, NULL, NULL},因为 Zend Engine需要靠它来确认这些导出函数的列表是否列举完毕。
注意:
1 )你不应该使用一个预定义的宏来代替列表的结尾部分(即{NULL, NULL, NULL}),因为编译器会尽量寻找一个名为 “NULL” 的函数的指针来代替 NULL!
2)宏 ZEND_FE(“Zend Function
Entry”的简写)将简单地展开为一个zend_function_entry结构。不过需要注意,这些宏对函数采取了一种很特别的命名机制:把你的C函数前加上一个
zif_前缀。比方说,ZEND_FE(hello_word)其实是指向了一个名为zif_hello_word()的
C函数。如果你想把宏和一个手工编码的函数名混合使用时(这并不是一个好的习惯),请你务必注意这一点。
小提示:如果出现了一些引用某个名为zif_*()函数的编译错误,那十有八九与 ZEND_FE 所定义的函数有关。
用来定义函数的宏
宏 |
说明 |
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_module_entry的结构,它包含了所有需要向 Zend提供的模块信息。zend_module_entry的内部声明”中看到这个 Zend模块的内部定义:
字段 |
说明 |
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 。 |
在我们的例子当中,这个结构被定义如下:
这基本上是你可以设定最简单、最小的一组值。该模块名称为"hello_module”,然后是所引用的函数列表,其后所有的启动和关闭函数都没有使用,均被设定为了。
作为参考,你可以在表 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搭配使用。 |
这个函数只用于动态可加载模块。我们先来看一下如何通过宏ZEND_GET_MODULE来创建这个函数:
这个函数的实现被一条件编译语句所包围。这是很有必要的,因为get_module()函数仅仅在你的模块想要编译成动态模块时才会被调用。通过在编译命令行指定编译条件:COMPILE_DL_FIRSTMOD(也就是上面我们设置的那个预定义)的打开与否,你就可以决定是编译成一个动态模块还是编译成一个内建模块。如果想要编译成内建模块的话,那么这个get_module()将被移除。
get_module()函数在模块加载时被 Zend所调用,你也可以认为是被你 PHP脚本中的dl()函数所调用。这个函数的作用就是把模块的信息信息块传递 Zend并通知 Zend 获取这个模块的相关内容。
如果你没有在一个动态可加载模块中实现get_module()函数,那么当你在访问它的时候 Zend 就会向你抛出一个错误信息。
导出函数的实现是我们构建扩展的最后一步。在我们的hello_module例子中,函数被实现如下:
这个函数是用宏 ZEND_FUNCTION来声明的,和前面我们讨论的 Zend函数块中的 ZEND_FE声明相对应。在函数的声明之后,我们的代码便开始检查和接收这个函数的参数。在将参数进行转换后将其值返回。
Zend允许模块在加载和卸载时收到通知,以进行初始化和清除工作,我们要做的就是把相应函数传递给Zend,它会在合适的时机自动调用。
Zend提供了如下四个宏,分别用于声明对应的函数:
宏 |
意义 |
ZEND_MODULE_STARTUP_D(hello_module) |
在加载模块时调用 |
ZEND_MODULE_SHUTDOWN_D(hello_module) |
在卸载模块时调用 |
ZEND_MODULE_ACTIVATE_D(hello_module) |
一个页面开始运行时调用 |
ZEND_MODULE_DEACTIVATE_D(hello_module) |
一个页面运行完毕时调用 |
在声明模块信息里面
1)STANDARD_MODULE_HEADER和STANDARD_MODULE_PROPERTIES宏填充了该结构的首尾部分,具体填充了什么并不是我们需要关心的,并且为了兼容后续版本也最好不要手工修改。
2)第二、三项是模块名称和导出函数,名称可以任意填写,导出函数就是我们在前面准备好的zend_function_entry数组。
3)后面的四个参数内容:
就是用于启动和终止函数,他们都是指针函数。
4) 参数值 PHP_MINFO(hello_module),也是指针函数,用于配合phpinfo()来显示模块信息。
这些宏的用法和ZEND_FUNCTION宏一样,展开后就是声明了特定原型的函数,其参数module可以是任意的,但最好使用模块名称。这些函数的参数中,对我们有用的是int module_number,它是模块号,全局唯一,后面会提到其用处。
在声明和实现相应函数时,都应该使用这些宏。最后,需要把这些函数填写到zend_module_entry里,可按顺序使用如下的宏,这些宏生成相应的函数名称:
ZEND_MODULE_STARTUP_N(hello_module) |
ZEND_MODULE_SHUTDOWN_N(hello_module) |
ZEND_MODULE_ACTIVATE_N(hello_module) |
ZEND_MODULE_DEACTIVATE_N(hello_module) |
在我们的hello_module中,对应的声明(由于ext_skel生成的代码是PHP API ,和ZEND API本质上一样)
对应的实现:
如果不想使用这个函数,对应的参数都可以设置为null
那么模块声明
标签:php扩展 处理 动态加载 source ble when width 存在 other
原文地址:http://www.cnblogs.com/ghjbk/p/6953610.html