###创建第一个PHP扩展函数
在PHP扩展中,创建一个函数主要需要经过三步:
- 在源文件(.c)中使用
PHP_FUNCTION
宏创建函数实现,并头文件中声明该函数 - 使用
PHP_FE
告诉zend_function_entry
结构体新创建的函数的地址 - 将
zend_function_entry
结构体注册到zend_module_entry
扩展入口结构体上,只有 创建第一个函数的时候需要这样做。
接下来,我们对这三个步骤展开,并且辅以一个名为demo_array()
的函数作为例子,该函数返回一个 我们在扩展函数中创建的数组作为返回值。
在讲解如何创建一个扩展函数之前,我们需要创建一个扩展的基本骨架,创建扩展的基本骨架请参考 PHP扩展开发 - 构建第一个PHP扩展。
在PHP扩展开发 - 构建第一个PHP扩展中,我们创建了一个名为ext_demo_1
的扩展程序,进入扩展目录, 我们将看到如下文件:
/vagrant/ext/ext_demo_1$ ls
config.m4 CREDITS ext_demo_1.c php_ext_demo_1.h
config.w32 EXPERIMENTAL ext_demo_1.php tests
首先,我们需要在ext_demo_1.c
文件中,创建函数的实现:
PHP_FUNCTION(demo_array)
{
zval *subarray;/* 子数组 */
array_init(return_value); /* 将函数返回值初始化为数组类型 */
/* 返回数组中添加三个值:life=>42, 123=>1, 124=>3.1415926 */
add_assoc_long(return_value, "life", 42);
add_index_bool(return_value, 123, 1);
add_next_index_double(return_value, 3.1415926);
/* 添加两个字符串值: 125=> Foo, 126=> Bar */
add_next_index_string(return_value, "Foo", 1);
add_next_index_string(return_value, estrdup("Bar"), 0);
/* 初始化zval结构体,分配内存空间 */
MAKE_STD_ZVAL(subarray);
array_init(subarray);
add_next_index_long(subarray, 1);
add_next_index_long(subarray, 20);
add_next_index_long(subarray, 132);
/* 将subarray添加到返回值 */
add_index_zval(return_value, 444, subarray);
}
创建函数体之后,我们需要在头文件php_ext_demo_1.h
中声明该函数。
PHP_FUNCTION(demo_array);
第二步是告诉zend_function_entry
结构体函数的地址。在ext_demo_1.c
文件的第 41 行左右, 我们可以看到zend_function_entry
结构体变量,将函数通过PHP_FE
宏添加到该变量数组中。
const zend_function_entry ext_demo_1_functions[] = {
PHP_FE(confirm_ext_demo_1_compiled, NULL) /* For testing, remove later. */
PHP_FE(demo_array, NULL) /* 在这里添加demo_array函数 */
PHP_FE_END /* Must be the last line in ext_demo_1_functions[] */
};
一般来说,如果使用的是ext_skel
创建的扩展骨架的话,一个函数就算是添加完成了,因为第三步在生成扩展骨架的时候已经自动的完成了, 这里的第三步就是将该ext_demo_1_functions
添加到zend_module_entry
结构体上。
zend_module_entry ext_demo_1_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
"ext_demo_1",
ext_demo_1_functions, /* 注意这里,添加了 ext_demo_1_functions 变量 */
PHP_MINIT(ext_demo_1),
PHP_MSHUTDOWN(ext_demo_1),
PHP_RINIT(ext_demo_1), /* Replace with NULL if there's nothing to do at request start */
PHP_RSHUTDOWN(ext_demo_1), /* Replace with NULL if there's nothing to do at request end */
PHP_MINFO(ext_demo_1),
#if ZEND_MODULE_API_NO >= 20010901
"0.1", /* Replace with version number for your extension */
#endif
STANDARD_MODULE_PROPERTIES
};
为了验证我们的函数创建是否成功,我们编译一下这个扩展:
# phpize
# ./configure
# make
# make install
注意: 如果之前编译过该扩展,需要先
make clean
一下,清理掉上次编译产生的中间文件。
安装完成之后,验证一下是否成功:
$ php -r "print_r(demo_array());"
Array
(
[life] => 42
[123] => 1
[124] => 3.1415926
[125] => Foo
[126] => Bar
[444] => Array
(
[0] => 1
[1] => 20
[2] => 132
)
)
好了,一个函数就已经创建完成了,在php文件中,我们就可以直接调用刚才创建的函数了:
<?php
print_r(demo_array());
###函数结构解析
为了对该函数的创建过程有个直观的了解,我们对刚才用到的宏进行简单的剖析。
这里的PHP_FUNCTION
实际上是Zend定义的一个宏,展开后如下:
#define PHP_FUNCTION(name) \
void zif_##name(INTERNAL_FUNCTION_PARAMETERS)
也就是说,如果有函数定义如下:
PHP_FUNCTION(sample_hello_world)
{
php_printf("Hello World!\n");
}
在编译的时候将会被替换为:
void zif_sample_hello_world(
int ht,
zval *return_value, /* 函数返回值 */
zval **return_value_ptr,
zval *this_ptr,
char return_value_used TSRMLS_DC /* 标识返回值是否被使用了 */
){
...
}
这里的 zif_ 是Zend Internal Functions 缩写,为了避免定义的函数与C内部函数名冲突。
对于函数demo_array
,内部实现如下:
ZEND_FUNCTION(demo_array)
{
...
}
部分宏替换之后如下:
void zif_demo_array(int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC)
{
...
}
当然,这样还没有结束,Zend引擎并不知道该函数的地址,因此,需要告诉引擎函数地址:
const zend_function_entry ext_demo_1_functions[] = {
PHP_FE(confirm_ext_demo_1_compiled, NULL) /* For testing, remove later. */
PHP_FE(demo_array, NULL)
PHP_FALIAS(demo_array_alias, demo_array, NULL) /* 函数别名 */
PHP_FE_END /* Must be the last line in ext_demo_1_functions[] */
};
注意,zend_function_entry结构体最后一组值为
PHP_FE_END
({ NULL, NULL, NULL, 0, 0 }),如果需要添加新的函数,则 在上面添加PHP_FE宏即可。
这里的
PHP_FALIAS
宏为函数demo_array提供了一个别名demo_array_alias。
使用zif前缀仍然可能与内部函数名称产生冲突,可以使用
PHP_NAMED_FUNCTION
和PHP_NAMED_FE
配合使用(与PHP_FUNCTION和PHP_FE一样)
这里的PHP_FE
定义如下:
#define PHP_FE ZEND_FE /* php.h:341 */
#define ZEND_FE(name, arg_info) ZEND_FENTRY(name, ZEND_FN(name), arg_info, 0) /* zend_API.h:77 */
#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 },
该宏需要两个参数,第一个参数为函数名,第二个为参数,用于提供参数提示信息。
我的博客:http://aicode.cc/,在这里可以看到更多相关文章。