扩展骨架文件内容
让我们回顾一下扩展骨架文件的内容。
config.m4
是扩展配置脚本,用于通过 phpize
或 buildconf
命令生成 configure
脚本。它是用 M4
宏处理语言编写的。对于 PHP 扩展配置来说,最基本的知识就足够了。您可以复制粘贴本教程或其他扩展配置文件中的代码块。
PHP_ARG_ENABLE([test],
[whether to enable test support],
[AS_HELP_STRING([--enable-test],
[Enable test support])],
[no])
if test "$PHP_TEST" != "no"; then
AC_DEFINE(HAVE_TEST, 1, [ Have test support ])
PHP_NEW_EXTENSION(test, test.c, $ext_shared)
fi
PHP_ARG_ENABLE(…) - 宏增加了一个配置选项 --enable-test
。它有三个值:yes
(是)、no
(否)和 shared
(共享)。
运行 phpize
时,默认值为 "shared",这意味着我们将创建一个可动态加载的 PHP 扩展。不过,可以将 test
扩展目录复制到主 PHP 发行版("ext/test")中,然后重新运行 ./buildconf
和 ./configure … -enable-test
,以静态链接的 test
扩展重新构建整个 PHP。
也可以默认启用扩展,将第 5 行的 no
改为 yes
。在这种情况下,可以通过./configure --disable-test
来禁用 test
扩展。
后面的 if
只是一段普通的 UNIX shell 代码,用于测试 --enable-test
、--disable-test
或 --enable-test=shared
所定义的值。
AC_DEFINE(HAVE_TEST) 在 config.h
中添加了 C 宏 HAVE_TEST
,因此必要时可以使用条件编译指令(#ifdef
、#ifndef
)跳过无用的代码。
最后,PHP_NEW_EXTENSION(test, test.c, $ext_shared)
宏表示我们将从 test.c
文件构建扩展名 test
。可以指定多个文件。根据变量 $ext_shared
的值,扩展可能以共享对象或静态链接的方式构建。(这与 --enable-test
选项相同)。
如果添加了新的源文件或需要链接一些外部库,可能需要对该文件进行扩展。稍后我将介绍如何链接外部库。在对该文件进行任何修改后,不要忘记重新运行 phpize/buildconf + configure
。
Windows PHP 使用不同的构建系统。对于 Windows,文件 config.w32
是 config.m4
的替代文件。这两个文件几乎一样。它们使用类似的宏,只是语言不同:在 Windows 下,PHP 编译系统使用 JavaScript 而不是 M4
和 Shell。关于宏的解释我就不重复了。你应该能猜到。
ARG_ENABLE('test', 'test support', 'no');
if (PHP_TEST != 'no') {
AC_DEFINE('HAVE_TEST', 1, 'test support enabled');
EXTENSION('test', 'test.c', null, '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1');
}
php_test.h
是一个包含常用定义的 C 语言头文件。在我们最基本的例子中,它定义了
-
test_module_entry
- 扩展描述结构。(它是扩展的入口) -
PHP_TEST_VERSION
- 扩展的版本。 -
ZEND_TSRMLS_CACHE_EXTERN
- 如果扩展是为线程安全编译(ZTS)并以共享对象(COMPILE_DL_TEST)编译的,则为线程本地存储缓存入口。
/* test extension for PHP */
#ifndef PHP_TEST_H
# define PHP_TEST_H
extern zend_module_entry test_module_entry;
# define phpext_test_ptr &test_module_entry
# define PHP_TEST_VERSION "0.1.0"
# if defined(ZTS) && defined(COMPILE_DL_TEST)
ZEND_TSRMLS_CACHE_EXTERN()
# endif
#endif /* PHP_TEST_H */
test.c
是主要的(在我们的例子中是唯一的)扩展源文件。由于文件太大,无法放在一个页面/屏幕上,所以我将把它分成几个小部分,分别进行说明。
/* test extension for PHP */
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "php.h"
#include "ext/standard/info.h"
#include "php_test.h"
包含必要的 C 头文件。如有必要,可添加其它 #include
指令。
/* For compatibility with older PHP versions */
#ifndef ZEND_PARSE_PARAMETERS_NONE
#define ZEND_PARSE_PARAMETERS_NONE() \
ZEND_PARSCE_PARAMETERS_START(0, 0) \
ZEND_PARSE_PARAMETERS_END()
#endif
一些向前兼容性宏,以便为较旧的 PHP-7 版本编译扩展。
/* {{{ void test_test1()
*/
PHP_FUNCTION(test_test1)
{
ZEND_PARSE_PARAMETERS_NONE();
php_printf("The extension %s is loaded and working!\r\n", "test");
}
/* }}} */
我们的 PHP 扩展程序提供的函数 test_test1()
的 C 语言代码。PHP_FUNCTION()
宏的参数是函数名。ZEND_PARSE_PARAMETERS_NONE()
表示该函数不需要任何参数。php_printf(…)
只是一个 C 函数调用,它将字符串打印到输出流中,类似于 PHP 的 printf()
函数。
/* {{{ string test_test2( [ string $var ] )
*/
PHP_FUNCTION(test_test2)
{
char *var = "World";
size_t var_len = sizeof("World") – 1;
zend_string *retval;
ZEND_PARSE_PARAMETERS_START(0, 1)
Z_PARAM_OPTIONAL
Z_PARAM_STRING(var, var_len)
ZEND_PARSE_PARAMETERS_END();
retval = strpprintf(0, "Hello %s", var);
RETURN_STR(retval);
}
/* }}}*/
另一个更复杂的函数使用 "快速参数解析 API" 来描述参数。
ZEND_PARSE_PARAMETERS_START(0, 1)
开始参数描述部分。它的第一个参数(0)定义了所需参数的个数。
第二个参数(1)定义了最大参数数。因此,我们的函数可以不带参数调用,也可以只带一个参数调用。
在这一部分中,我们应定义所有参数、参数类型以及参数的复制位置。例如:
-
Z_PARAM_OPTIONAL 用于区分必选参数和可选参数。
-
Z_PARAM_STRING() 定义了一个字符串参数,其值将被复制到变量
var
,其长度将被复制到变量var_len
。
请注意,我们的参数是可选的,因此可以省略。在这种情况下,将使用默认值 World
。参见 ZEND_PARSE_PARAMETERS_START
上变量 var
和 var_len
的初始化。
代码会创建一个 zend_string
值,并通过类似 PHP sprintf()
函数的宏 RETURN_STR()
返回。
下面是钩子函数:
/* {{{ PHP_RINIT_FUNCTION
*/
PHP_RINIT_FUNCTION(test)
{
#if defined(ZTS) && defined(COMPILE_DL_TEST)
ZEND_TSRMLS_CACHE_UPDATE();
#endif
return SUCCESS;
}
/* }}} */
PHP_RINIT_FUNCTION()
宏定义了一个在每次请求初始化时都要调用的回调函数。该函数在 PHP 扩展开发中非常有用,可以用于每个请求的初始化工作,如重置状态、分配资源等。在我们的例子中,它只初始化了线程本地存储缓存。如果能尽早(在 MINIT
或 GINIT
回调中)完成这项工作,效果会更好。我预测这个问题会在 PHP 8 扩展骨架中得到解决。
/* {{{ PHP_MINFO_FUNCTION
*/
PHP_MINFO_FUNCTION(test)
{
php_info_print_table_start();
php_info_print_table_header(2, "test support", "enabled");
php_info_print_table_end();
}
/* }}} */
PHP_MINFO_FUNCTION()
定义了一个从 PHP phpinfo()
函数调用的回调函数,用于打印有关扩展的信息。
函数参数:
/* {{{ arginfo
*/
ZEND_BEGIN_ARG_INFO(arginfo_test_test1, 0)
ZEND_END_ARG_INFO()
第一个函数的参数信息。没有参数。
ZEND_BEGIN_ARG_INFO(arginfo_test_test2, 0)
ZEND_ARG_INFO(0, str)
ZEND_END_ARG_INFO()
/* }}} */
第二个函数的参数信息。名称为 str
的单个可选参数按值传递。
/* {{{ test_functions[]
*/
static const zend_function_entry test_functions[] = {
PHP_FE(test_test1, arginfo_test_test1)
PHP_FE(test_test2, arginfo_test_test2)
PHP_FE_END
};
/* }}} */
test_functions
是一个包含所有扩展函数及其参数信息的列表。该列表以 PHP_FE_END
宏结束。
/* {{{ test_module_entry
*/
zend_module_entry test_module_entry = {
STANDARD_MODULE_HEADER,
"test", /* Extension name */
test_functions, /* zend_function_entry */
NULL, /* PHP_MINIT - Module initialization */
NULL, /* PHP_MSHUTDOWN - Module shutdown */
PHP_RINIT(test), /* PHP_RINIT - Request initialization */
NULL, /* PHP_RSHUTDOWN - Request shutdown */
PHP_MINFO(test), /* PHP_MINFO - Module info */
PHP_TEST_VERSION, /* Version */
STANDARD_MODULE_PROPERTIES
};
/* }}} */
test_module_entry
是主要的扩展入口结构。PHP core 从这种结构中获取有关扩展的所有信息。它定义了:
-
扩展名("test")。
-
已声明的 PHP 函数列表("test_functions")。
-
一些回调函数和扩展版本(PHP_TEST_VERSION - 在头文件中定义)。
回调发生在 PHP 启动时 (MINIT
)、PHP 终止时 (MSHUTDOWN
)、每次请求处理开始时 (RINIT
)、每次请求处理结束时 (RSHUTDOWN
) 以及 phpinfo()
时 (MINFO
)。
#ifdef COMPILE_DL_TEST
# ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
# endif
ZEND_GET_MODULE(test)
#endif
在 PHP 扩展开发中,COMPILE_DL_<extension> 和 ZEND_GET_MODULE(<extension>) 宏配合使用,以支持扩展的动态加载(dl() 函数)。
最后,给动态链接下几个定义。