扩展骨架文件内容

让我们回顾一下扩展骨架文件的内容。

config.m4 是扩展配置脚本,用于通过 phpizebuildconf 命令生成 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.w32config.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 上变量 varvar_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 扩展开发中非常有用,可以用于每个请求的初始化工作,如重置状态、分配资源等。在我们的例子中,它只初始化了线程本地存储缓存。如果能尽早(在 MINITGINIT 回调中)完成这项工作,效果会更好。我预测这个问题会在 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() 函数)。

最后,给动态链接下几个定义。