[ PHP 内核与扩展开发系列] 第一个 PHP 扩展:编写函数

前面我们已经生成好了一份扩展框架,但它是没有什么实际作用。一个扩展的作用可大了去了,既可以操作 PHP 中的变量、常量,还可以定义函数、类、方法、资源等。先让我们从函数说起吧!

ZEND_FUNCTION()宏函数

ZEND_FUNCTION() 宏函数也可以写成 PHP_FUNCTION(),但 ZEND_FUNCTION() 更前卫、标准一些,但两者是完全相同的。

#define PHP_FUNCTION    ZEND_FUNCTION

#define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(ZEND_FN(name))
#define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS)
#define ZEND_FN(name) zif_##name

其中,zif 是 Zend Internal Function 的意思,zif_ 前缀是可供 PHP 语言调用的函数在 C 语言中对应函数的名称前缀。

ZEND_FUNCTION(academy_hello)
{
    php_printf("Hello Laravel Academy!\n");
}

上面定义了一个函数,在 C 语言中展开后应该是这样的:

void zif_academy_hello(INTERNAL_FUNCTION_PARAMETERS)
{
    php_printf("Hello Laravel Academy!\n");
}

上面的展开式仅供参考,绝不推荐在编程时使用,我们应该采用宏的形式,来提高程序的兼容性与可读性。上面的代码定义了一个可供用户在 PHP 语言中调用的函数实现,但现在用户还不能在程序中调用,因为这个函数还没有与用户端建立联系,也就是说虽然我们在 C 语言中完成了它的实现,但用户端 PHP 语言还根本不知道它的存在呢。

现在我们回头看一下前面章节中我们为扩展定义的 zend_module_entry academy_module_entry(它是联系 C 扩展与 PHP 语言的重要纽带)中的 NULL, /* Functions */,当时我们为它赋予了 NULL,是因为还没有函数,现在我们已经为它编写了函数了,便可以给它赋予一个新值了,这个值需要是 zend_function_entry[] 类型的,首先让我们来构造这个重要数据:

static zend_function_entry academy_functions[] = {
    ZEND_FE(academy_hello, NULL)
    { NULL, NULL, NULL }
};

下面是 ZEND_FE 的定义:

#define ZEND_FE(name, arg_info) 
    ZEND_FENTRY(name, ZEND_FN(name), arg_info, 0)
#define ZEND_FENTRY(zend_name, name, arg_info, flags)
    { #zend_name, name, arg_info, (zend_uint) (sizeof(arg_info)/sizeof(struct _zend_arg_info)-1), flags },

ZEND_FE(academy_hello, NULL) 展开后便是:

{"academy_hello", zif_academy_hello, NULL, (zend_uint) (sizeof(NULL)/sizeof(struct _zend_arg_info)-1), 0},

其中最后的 {NULL,NULL,NULL} 是固定不变的。ZEND_FE() 宏函数是对我们 academy_hello 函数的一个声明,如果我们有多个函数,可以直接以类似的形式添加到 {NULL,NULL,NULL} 之前,注意每个之间不需要加逗号。其中的 arg_info 我们现在先赋予 NULL 就行了,我们将在后续章节讨论这个参数。确保一切无误后,我们替换掉 zend_module_entry 里的原有成员,现在应该是这样的:

ZEND_FUNCTION(academy_hello)
{
    php_printf("Hello Laravel Academy!\n");
}

static zend_function_entry academy_functions[] = {
    ZEND_FE(academy_hello,NULL)
    { NULL, NULL, NULL }
};

zend_module_entry academy_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
     STANDARD_MODULE_HEADER,
#endif
    "academy", //这个地方是扩展名称,往往我们会在这个地方使用一个宏。
    academy_functions, /* Functions */
    NULL, /* MINIT */
    NULL, /* MSHUTDOWN */
    NULL, /* RINIT */
    NULL, /* RSHUTDOWN */
    NULL, /* MINFO */
#if ZEND_MODULE_API_NO >= 20010901
    "2.1", //这个地方是我们扩展的版本
#endif
    STANDARD_MODULE_PROPERTIES
};

在扩展目录下依次执行如下命令:

./configure --with-php-config=/usr/local/php54/bin/php-config
make
sudo make install

用下面这个命令来测试下,应该会输出 Hello Laravel Academy 了,如果没有输出,说明你哪个地方做错了:

$ php -r 'academy_hello();'

Zend Internal Functions

zif_ 前缀在前面我们已经说过了,代表着 Zend Internal Function,主要用来避免命名冲突,比如PHP 语言中有个 strlen() 函数,而 C 语言中也有 strlen() 函数,所以 PHP 中的 strlen 在 C 中的实现不能是 strlen,而应改成一个其他不同的名字。

但是有些时候尽管我们加了 zif_ 前缀,还会出现一些冲突问题,比如函数名称本身是一个宏名称从而被编译器替换掉了。在这种情况下,我们需要手动来为我们扩展中的函数命名,这一步操作通过ZEND_NAMED_FUNCTION(diy_academy_hello) 来代替 ZEND_FUNCTION(academy_hello)。前者由我们指定名称,后者会自动加上前缀 zif_

如果我们在定义函数时使用了 ZEND_NAMED_FUNCTION(),那么在 academy_functions[] 里,我们需要用 ZEND_NAMED_FE() 宏来代替 ZEND_FE() 宏。即:ZEND_NAMED_FE(academy_hello, diy_academy_hello, NULL)。这一点在 ext/standard/file.c 里用到了,我们可以看 fopen() 函数的定义:PHP_NAMED_FUNCTION(php_if_fopen)。但是用户端不会感觉到任何变化,还是用 fopen 函数,因为 zend_function_entry 中每一项的第一个值代表这此函数在PHP 语言中的名称。

函数别名

去 PHP 手册里查一下 pos() 函数,会得到这么一条信息:”This function is an alias of: current()”,也就是说,它只是 current 的一个软链接而已,类似 Linux 中的 ln -s 命令,理解成 Windows 下的快捷方式也成。运行 pos 函数,其实就是在运行 current 函数。这往往是因为版本升级引起的,新版本中的程序提供了某个功能的新的实现,先为原来的函数改个名,但还需要保留原来的函数名,所以这就用到了别名。这个功能可以在内核中通过 ZEND_NAMED_FE 宏来实现。

static zend_function_entry academy_functions[] = {
    ZEND_FE(academy_hello,NULL)
    ZEND_NAMED_FE(academy_hi,ZEND_FN(academy_hello),NULL)
    { NULL, NULL, NULL }
};

ZEND_NAMED_FE 也可以写成 PHP_NAMED_FE,但推荐用前,其宏定义如下:

#define ZEND_NAMED_FE(zend_name, name, arg_info)          ZEND_FENTRY(zend_name, name, arg_info, 0)

通过 ZEND_NAMED_FE 的展开式我们了解到,它只是把 PHP 语言中的两个函数的名字对应到同一个 C 语言函数而已。其实还有另外一种写法:

static zend_function_entry academy_functions[] = {
    ZEND_FE(academy_hello,NULL)
    ZEND_FALIAS(academy_hi,academy_hello,NULL)
    { NULL, NULL, NULL }
};

/*
#define ZEND_FALIAS(name, alias, arg_info)          ZEND_FENTRY(name, ZEND_FN(alias), arg_info, 0)
*/

可见,展开式是一样的。执行下面两个函数,实现功能完全一致:

在这一章节里,我们学会了如何创建一个 PHP 扩展并为其添加函数,并编译到 PHP 中供用户在 PHP 语言中调用。在接下来的章节里,我们将陆续看到更多高级的 PHP 内核特性,从而使我们编写出更好的 PHP 扩展。

学院君 has written 699 articles

资深PHP工程师,Laravel学院院长

发表评论

标记为*的字段是必填项(邮箱地址不会被公开)

你可以使用这些HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>