Zend API:深入 PHP 内核(十五) 启动函数与关闭函数
启动函数和关闭函数会在模块的(载入时)初始化和(卸载时)反初始化时被调用,而且只调用这一次。正如我们在本章前面(见 Zend 模块描述块的说明)所提到的,它们是模块和请求启动和关闭时所发生的事件。
模块启动/关闭函数会在模块加载和卸载时被调用。请求启动/关闭函数会在每次处理一个请求时(也就是在执行一个脚本文件时)被调用。
对于动态加载的扩展而言,模块和请求的启动函数与模块和请求的关闭函数都是同时发生的(严格来说模块启动函数是先于请求启动函数被调用的,译注)。
可以用某些宏来声明和实现这些函数,详情请参阅前面的关于“Zend 模块声明”的讨论。
Zend 扩展优先于PHP扩展加载。同一级别(比如均为Zend扩展或PHP扩展)则依照php.ini里面extension= xxx指令的先后顺序来加载。
Zend API:深入 PHP 内核(十六) 调用用户函数
PHP 还允许你在你的模块里面调用一些一些用户定义的函数,这样在实现某些回调机制(比如在做一些数组的轮循(array walking)、搜索或设计一些简单的事件驱动的程序时)时会很方便。
我们可以通过调用 call_user_function_ex() 来调用用户函数。它需要你即将访问函数表的指针、这个对象的指针(假如你访问的是类的一个方法的话),函数名、返回值、参数个数、具体的参数数组和一个是否需要进行 zval 分离的标识(这个函数原型已经“过时”了,至少是从 PHP 4.2 开始这个函数就追加了一个 HashTable *symbol_table 参数。下面所列举的函数原型更像是 call_user_function () 的声明。译注)。
ZEND_APIint call_user_function_ex (
HashTable *function_table,
zval *object,
zval *function_name,
zval **retval_ptr_ptr,
int param_count,
zval **params[],
int no_separation
);
需要注意的是你不必同时指定 function_table 和object 这两个参数,只需要指定其中一个就行了。不过如果你想调用一个方法的话,那你就必须提供一个包含此方法的对象。这时call_user_function()会自动将函数表设置为当前这个对象的函数表。而对于其他情况,只需要设定一下 function_table 而把object 设为NULL 就行了。
一般情况下,默认的函数表是包含有所有函数的“根”函数表.这个函数表是编译器全局变量的一部分,
你可以通过CG()宏来访问它.如果想把编译器全局变量引入你的函数,只需先执行一下TSRMLS_FETCH 宏就可以了。
而调用的函数名是保存在一个 zval 容器内的。猛一下你可能会感到好奇,但其实这是很合乎逻辑的。想想看,既然我们在脚本中的大部分时间都是在接收一个函数名作为参数,并且这个参数还是被转换成(或被包含在)一个 zval 容器。那还不如现在就直接把这个 zval 容器传送给函数,只是这个 zval 容器的类型必须为 IS_STRING。
下一个参数是返回值 return_value 的指针。这个容器的空间函数会自动帮你申请,所以我们无需手动申请,但在事后这个容器空间的销毁释放工作得由我们自己(使用zval_dtor())来做。
跟在return_value 后面的是一个标识参数个数的整数和一个包含具体参数的数组。最后一个参数 no_separation 指明了函数是否禁止进行 zval 分离操作。这个参数应该总是设为 0,因为如果设为1 的话那这个函数会节省一些空间但要是其中任何一个参数需要做 zval 分离时都会导致操作失败。
“例 3.15 调用用户函数”向我们展示如何去调用一个脚本中的用户函数。这段代码调用了一个我们模块所提供的call_userland() 函数。模块中的 call_userland() 函数会调用脚本中一个名为它的参数的用户函数,并且将这个用户函数的返回值直接作为自己的返回值返回脚本。
另外你可能注意到了我们在最后调用了析构函数。这个操作或许没有太大必要(因为这些值都应该是分离过的,对它们的赋值将会很安全),但这么做总没有什么坏处,说不定在某个关键时刻它成为我们的一道“免死金牌”
例3.15 调用用户函数
zval**function_name;
zval*retval;
if((ZEND_NUM_ARGS()!= 1) ||(zend_get_parameters_ex(1,&function_name) !=SUCCESS)){
WRONG_PARAM_COUNT;
}
if((*function_name)->type!= IS_STRING){
zend_error(E_ERROR, “Functionrequires string argument”);
}
TSRMSLS_FETCH();
//把编译器全局变量引入函数
if(call_user_function_ex(CG(function_table),NULL, *function_name, &retval, 0, NULL, 0) !=SUCCESS){
zend_error(E_ERROR, “Functioncall failed”);
}
zend_printf(”Wehave %i as type\n”, retval->type);
*return_value= *retval;
zval_copy_ctor(return_value);
zval_ptr_dtor(&retval);
调用脚本:
<?php
dl("call_userland.so");
function test_function(){
echo "We are in the testfunction!\n";
return 'hello';
}
$return_value =call_userland("test_function");
echo "Return value: '$return_value'";
?>
上例将输出:
Weare in the test function! Wehave 3 as type Return value: ‘hello’
Zend API:深入 PHP 内核(十七) 支持初始化文件(php.ini)
PHP4 重写了对初始化文件的支持。现在你可以直接在代码中指定一些初始化选项,然后在运行时读取和改变这些选项值,甚至还可以在选项值改变时接到相关通知。
如果想要为你的模块创建一个 .ini 文件的配置节,可以使用宏PHP_INI_BEGIN() 来标识这个节的开始,并用PHP_INI_END()表示该配置节已经结束。然后在两者之间我们用 PHP_INI_ENTRY() 来创建具体的配置项。
PHP_INI_BEGIN()
PHP_INI_ENTRY("first_ini_entry", "has_string_value", PHP_INI_ALL,NULL)
PHP_INI_ENTRY("second_ini_entry", "2", PHP_INI_SYSTEM,OnChangeSecond)
PHP_INI_ENTRY("third_ini_entry", "xyz", PHP_INI_USER, NULL)
PHP_INI_END()
PHP_INI_ENTRY()总共接收 4 个参数:
1 配置项名称
2 初始值
3 改变这些值所需的权限
4 以及在值改变时用于接收通知的函数句柄。
配置项名称和初始值必须是一个字符串,即使它们是一个整数。
更改这些值所需的权限可以划分为三种:
PHP_INI_SYSTEM 只允许在 php.ini 中改变这些值;
PHP_INI_USER 允许用户在运行时通过像 .htaccess 这样的附加文件来重写其值;
PHP_INI_ALL 则允许随意更改。
而其实还有第四种权限:
PHP_INI_PERDIR,不过我们还暂时不能确定它有什么影响。
(本段关于这几种权限的说明与手册中《附录 G php.ini 配置选项》一节的描述略有出入。根据译者自己查到的资料,相比之下还是《附录G php.ini 配置选项》更为准确些。译注)
第四个参数是初始值被改变时接收通知的函数句柄。一旦某个初始值被改变,那么相应的函数就会被调用。这个函数我们可以用宏 PHP_INI_MH 来定义:
PHP_INI_MH(OnChangeSecond);
//handler for ini-entry "second_ini_entry"
//specify ini-entries here
PHP_INI_MH(OnChangeSecond){
zend_printf(”Messagecaught, our ini entry has been changed to %s<br>”,
new_value);
return(SUCCESS);
}
改变后的新值将会以字符串的形式并通过一个名为new_value 变量传递给函数。要是再注意一下PHP_INI_MH 的定义就会发现,我们实际上用到了不少参数:
#definePHP_INI_MH(name) int name(
php_ini_entry *entry,
char *new_value,
uint new_value_length,
void *mh_arg1,
void *mh_arg2,
void *mh_arg3
)
这些定义都可以在 php_ini.h 文件里找到。可以发现,我们的通知接收函数可以访问整个配置项、改变后的新值以及它的长度和其他三个可选参数。这几个可选参数可以通过PHP_INI_ENTRY1(携带一个附加参数)、PHP_INI_ENTRY2(携带两个附加参数)、PHP_INI_ENTRY3(携带三个附加参数)等宏来加以指定。
关于值改变的通知函数应该被用来本地缓存一些初始化选项以便可以更快地对其访问或被用来从事一个值发生改变时所要求完成的任务。比如要是一个模块对一个主机常量进行了连接,而这时有人改变了主机名,那么就需要自动地关闭原来的连接,并尝试进行新的连接。
可以使用“表 3.17 PHP 中用以访问初始化配置项的宏”来访问初始化配置项:
表3.17 PHP 中用以访问初始化配置项的宏
宏 | 说明 |
INI_INT(name) | 将配置项 name 的当前值以长整数返回。 |
INI_FLT(name) | 将配置项 name 的当前值以双精度浮点数返回。 |
INI_STR(name) | 将配置项 name 的当前值以字符串返回。 注意:这个字符串不是复制过的字符串,而是直接指向了内部数据。如果你需要进行进一步的访问的话,那就需要再进行复制一下。 |
INI_BOOL(name) | 将配置项 name 的当前值以布尔值返回。(返回值被定义为zend_bool,也就是说是一个unsigned char)。 |
INI_ORIG_INT(name) | 将配置项 name 的原始值以长整型数返回。 |
INI_ORIG_FLT(name) | 将配置项 name 的原始值以双精度浮点数返回。 |
INI_ORIG_STR(name) | 将配置项 name 的原始值以字符串返回。 注意:这个字符串不是复制过的字符串,而是直接指向了内部数据。如果你需要进行进一步的访问的话,那就需要再进行复制一下。 |
INI_ORIG_BOOL(name) | 将配置项 name 的原始值以布尔值返回。(返回值被定义为zend_bool,也就是说是一个unsigned char)。 |
最后,你还得把整个初始化配置项引入PHP。这项工作可以在模块的起始/结束函数中使用宏REGISTER_INI_ENTRIES()和UNREGISTER_INI_ENTRIES()来搞定。
ZEND_MINIT_FUNCTION(mymodule){
REGISTER_INI_ENTRIES();
}
ZEND_MSHUTDOWN_FUNCTION(mymodule){
UNREGISTER_INI_ENTRIES();
}
Zend API:深入 PHP 内核(十八) 何去何从
现在你已经掌握了很多关于 PHP 的知识了。你已经知道了如何创建一个动态加载的模块或被静态连接的扩展。你还知道了在PHP 和 Zend 的内部变量是如何储存的,以及如何创建和访问这些变量。
另外你也知道了很多诸如输出信息文本、自动将变量引入符号表等一系列工具函数的应用。
尽管这一章常常有点“参考”的意味,但我们还是希望它能给你一些关于如何开始编写自己的扩展这方面的知识。限于篇幅,我们不得不省略了很多东西。我们建议你花些时间仔细研究一下它的头文件和一些模块(尤其是ext/standard 目录下的一些文件以及 MySQL 模块,看一下这些众所周知的函数究竟是怎么实现的) 看一下别人是怎么使用这些 API 函数的,尤其是那些本章没有提到的那些函数。
Zend API:深入 PHP 内核(十九) 参考:关于配置文件的一些宏
由buildconf 处理的配置文件 config.m4 包含了所有在配置过程中所执行的指令。这些指令诸如包含测试包含所需的外部文件,像头文件、库文件等等。PHP 定义了一系列处理这类情况的宏,其中最常用的我们已经在“表 3.18 config.m4 中的M4 宏”列了出来。
表3.18 config.m4 中的M4 宏
宏 | 说明 |
AC_MSG_CHECKING(message) | 在执行 configure 命令时输出“checking <message>”等信息。 |
AC_MSG_RESULT(value) | 取得AC_MSG_CHECKING的执行结果,一般情况下value应为yes或no |
AC_MSG_ERROR(message) | 在执行configure命令时输出一条错误消息message并中止脚本的执行 |
AC_DEFINE(name,value,description) | 向php_config.h添加一行定义:#define name value // description (这对模块的条件编译很有用) |
AC_ADD_INCLUDE(path) | 添加一条编译器的包含路径,比如用于模块需要为头文件添加搜索路径 |
AC_ADD_LIBRARY_WITH_PATH (libraryname,librarypath) | 指定一个库的连接路径 |
AC_ARG_WITH(modulename,description,unconditionaltest,conditionaltest) | 这是一款比较强大的宏,用于将模块的描述description添加到“configure –help”命令的输出里面.PHP 会检查当前执行的configure脚本里面有没有–with-<modulename>这个选项.如果有则执行unconditionaltest语句(比如–with-myext=yes等), 此时,选项的值会被包含在$withval变量里面.否则就执行conditionaltest语句 |
PHP_EXTENSION(modulename, [shared]) | 这个是配置你的扩展时 PHP必定调用的一个宏。你可以在模块名后面提供第二个参数,用来表明是否将其编译为动态共享模块。这会导致在编译时为你的源码提供一个COMPILE_DL_<modulename>的定义。 |
Zend API:深入 PHP 内核(二十) API 宏
下面(见表 3.19 访问 zval 容器的API 宏)是一些引入到 Zend API 里面用于访问 zval 容器的API 宏。
宏 | 指向 |
Z_LVAL(zval) | (zval).value.lval |
Z_DVAL(zval) | (zval).value.dval |
Z_STRVAL(zval) | (zval).value.str.val |
Z_STRLEN(zval) | (zval).value.str.len |
Z_ARRVAL(zval) | (zval).value.ht |
Z_LVAL_P(zval) | (*zval).value.lval |
Z_DVAL_P(zval) | (*zval).value.dval |
Z_STRVAL_P(zval_p) | (*zval).value.str.val |
Z_STRLEN_P(zval_p) | (*zval).value.str.len |
Z_ARRVAL_P(zval_p) | (*zval).value.ht |
Z_LVAL_PP(zval_pp) | (**zval).value.lval |
Z_DVAL_PP(zval_pp) | (**zval).value.dval |
Z_STRVAL_PP(zval_pp) | (**zval).value.str.va |
Z_STRLEN_PP(zval_pp) | (**zval).value.str.len |
Z_ARRVAL_PP(zval_pp) | (**zval).value.ht |