PHP7扩展开发教程[3] – 怎样定义函数?
确定你已经读完上一章《PHP7扩展开发教程[2] – 怎样定义依赖扩展与INI配置项》。
随着知识点向前推进,越来越多的概念要被牵扯进来,难免会对新手造成一些学习负担,所以这里我要提醒一下如何保证学习质量。
大家一定要随手打开php7的源代码,在示例中出现的结构体,宏定义一定要亲自去翻看,这样才能一窥究竟,掌握自己探索源码的本领。一味偷懒走马观花的学习本教程,难以达到学习效果,读一百遍不如自己动手敲一遍,本教程的目的是带领大家掌握核心概念,仅仅是抛个砖,最终还是要靠大家自己去拓展和形成自己的认知体系。
正式开始
本文代码示例在:https://github.com/owenliang/php7-extension-explore/tree/master/course3-how-to-define-functions。
在章节2中的zend_module_entry中,扩展的functions字段传NULL表示没有注册任何函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
zend_module_entry module = { STANDARD_MODULE_HEADER_EX, // size,zend_api,zend_debug,zts NULL, // ini_entry NULL, // deps "myext", //name NULL, // functions extension_startup, // module_startup_func extension_shutdown, // module_shutdown_func extension_before_request, // request_startup_func extension_after_request, // request_shutdown_func extension_info, // info_func "1.0", // version // globals_size,globals_ptr,globals_ctor,globals_dtor,post_deactivate_func,module_started,type, // handle,module_number,build_id STANDARD_MODULE_PROPERTIES, }; |
我们可以通过赋值该字段完成全局函数的注册。
在这里,我决定注册2个函数:my_strtolower和my_strtoupper,用于字符串大小写转换。因此,我们现在定义一个zend_function_entry数组如下:
1 2 3 4 5 6 |
zend_function_entry funcs[] = { // fname,handler,arg_info,,num_args,flags {"my_strtolower", zif_strtolower, myext_strtolwer_arginfo, 1, 0}, {"my_strtoupper", zif_strtoupper, myext_strtoupper_arginfo, 1, 0}, {NULL, NULL, NULL, 0, 0}, }; |
它列举了myext扩展要导出的2个函数,这个数组同样以空元素结尾。
zend_function_entry定义在Zend/zend_API.h中:
1 2 3 4 5 6 7 |
typedef struct _zend_function_entry { const char *fname; void (*handler)(INTERNAL_FUNCTION_PARAMETERS); const struct _zend_internal_arg_info *arg_info; uint32_t num_args; uint32_t flags; } zend_function_entry; |
fname是PHP函数名,handler是对应的C函数指针,arg_info是描述函数的参数信息,num_args描述了参数的个数,flags对于全局函数没有意义因此传0(对于类成员函数有意义,例如private,final等描述信息)。
以PHP函数myext_strtolower为例,我们传了zif_strtolower这个函数指针作为其实现:
1 2 3 4 5 6 7 8 9 10 11 12 |
// Equals to PHP_FUNCTION(strtolwer) // // zif_ means Zend Internal Function void zif_strtolower(zend_execute_data *execute_data, zval *return_value) { TRACE("zif_strtolower"); int num_args = ZEND_CALL_NUM_ARGS(execute_data); zval *args = ZEND_CALL_ARG(execute_data, 1); TRACE("num_args=%d", num_args); *return_value = strcase_convert(&args[0], 1); } |
实现函数的参数是Zend要求的,execute_data是执行函数时的各种信息,主要用途是获取传入的参数。return_value用作函数返回值,我们只需要把返回值填充进去即可。函数里面的实现代码稍后再作解释。
为什么函数名要以zif_开头呢?这个主要是Zend惯用习惯,表示Zend Internal Fcuntion,就是指通过C语言实现的PHP函数,与用户在PHP里实现的Function相对。绝大多数扩展会通过宏PHP_FUNCTION(名称)来定义zif函数:
1 2 3 4 5 6 7 8 9 10 |
// 来自main/php.h #define PHP_FUNCTION ZEND_FUNCTION // 来自Zend/zend_API.h #define ZEND_FN(name) zif_##name #define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS) #define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(ZEND_FN(name)) // 来自Zend/zend.h #define INTERNAL_FUNCTION_PARAMETERS zend_execute_data *execute_data, zval *return_value |
最终的结果就是zif_函数名这样的样子,而本教程的原则是尽量避免使用宏(因为实在没有什么营养),以便展示出Zend的原始面目,更加便于大家理解Zend。
接着看一下myext_strtolower_arginfo/myext_strtoupper_arginfo这2个函数的参数描述信息是如何构造的:
1 2 3 4 5 6 7 8 9 10 11 |
// zif_strtolower's params defination zend_internal_arg_info myext_strtolwer_arginfo[] = { // required_num_args(interger stored in pointer) {(const char *)(zend_uintptr_t)1, NULL, 0, 0, 0, 0}, // name, class_name, type_hint, pass_by_reference, allow_null, is_variadic {"string", NULL, IS_STRING, 0, 0, 0}, }; zend_internal_arg_info myext_strtoupper_arginfo[] = { {(const char *)(zend_uintptr_t)1, NULL, 0, 0, 0, 0}, {"string", NULL, IS_STRING, 0, 0, 0}, }; |
以myext_strtolower_arginfo为例,它描述了myext_strtolower这个PHP函数的参数列表。
它必须是一个zend_internal_arg_info数组,每个元素代表一个参数,先来看一下zend_internal_arg_info的结构体定义:
1 2 3 4 5 6 7 8 9 |
/* arg_info for internal functions */ typedef struct _zend_internal_arg_info { const char *name; const char *class_name; zend_uchar type_hint; zend_uchar pass_by_reference; zend_bool allow_null; zend_bool is_variadic; } zend_internal_arg_info; |
name是参数的变量名称,class_name可以指定参数属于什么PHP类(PHP class),type_hint可以指定参数的类型(IS_STRING,IS_LONG,IS_ARRAY…),pass_by_references表示这个参数是否需要引用进来(PHP里的&),allow_null表示是否允许参数传NULL,is_variadic表示函数是否使用不定参数(function xxx($a, $b, …){ func_get_args(); ….})。
上述字段都是用来描述一个PHP函数的参数信息的,最好能如实详细的填充进去,当然这些描述信息绝大多数不是强制的,即便不写也不影响使用。
回到代码中来。非常特殊的是,数组第1个元素并不描述任何参数,而是描述整个参数列表的必传参数的个数,不过我发现这个信息也并不是强制执行的,即便我通过(const char *)(zend_uintptr_t)1限制了必传1个参数,在调用时仍旧可以传0个参数。
数组的第2个元素开始定义真实的参数,名字叫做string(对应PHP里$string),其类型type_hint是字符串(IS_STRING),这个type_hint属性还是挺有价值的,如果用户传入的是数字这种类型,Zend会帮我们先转成string。pass_by_reference传0,按值传递即可,PHP里很少需要传引用。allow_null传0,这个函数的string参数必须非NULL,is_variadic传0不支持变参。
最终需要将arginfo数组传给对应的zend_function_entry,同时赋值zend_function_entry的num_args为arginfo数组的长度(不包含第0个元素),我的2个函数都是接受1个参数,所以传长度1。
接下来
现在,可以把定义好的函数列表传给zend_module_entry完成注册:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
zend_function_entry funcs[] = { // fname,handler,arg_info,,num_args,flags {"my_strtolower", zif_strtolower, myext_strtolwer_arginfo, 1, 0}, {"my_strtoupper", zif_strtoupper, myext_strtoupper_arginfo, 1, 0}, {NULL, NULL, NULL, 0, 0}, }; zend_module_entry module = { STANDARD_MODULE_HEADER_EX, // size,zend_api,zend_debug,zts NULL, // ini_entry NULL, // deps "myext", //name funcs, // functions extension_startup, // module_startup_func extension_shutdown, // module_shutdown_func extension_before_request, // request_startup_func extension_after_request, // request_shutdown_func extension_info, // info_func "1.0", // version // globals_size,globals_ptr,globals_ctor,globals_dtor,post_deactivate_func,module_started,type, // handle,module_number,build_id STANDARD_MODULE_PROPERTIES, }; |
functions字段已经设置为funcs,这样我们的2个函数myext_strtolower和myext_strtoupper就注册成功了。
但是目前2个函数的实现还没有讲解,里面的内容我计划在下一章详细来说,在这里我们先看一下大概:
1 2 3 4 5 6 7 8 9 |
void zif_strtolower(zend_execute_data *execute_data, zval *return_value) { TRACE("zif_strtolower"); int num_args = ZEND_CALL_NUM_ARGS(execute_data); zval *args = ZEND_CALL_ARG(execute_data, 1); TRACE("num_args=%d", num_args); *return_value = strcase_convert(&args[0], 1); } |
函数通过2个ZEND的宏,获取了参数的个数和参数数组,然后调用了一定自定义的C函数strcase_convert将第一个参数传入得到返回值,赋值给了return_value作为最终的函数返回值。
在PHP里我们可以这样验证:
1 2 3 |
<?php echo my_strtolower("HELLO") . my_strtoupper("php") . my_strtolower(2017) . PHP_EOL; |
它会打印出:
helloPHP2017
结语
本章你应该掌握:
- 定义zif函数
- 描述函数的参数信息
- 注册函数
在下一章中,我们需要重点了解PHP7的zval设计。zval是Zend层的值类型,它表达了多种数据类型(整形,字符串,数组,对象…),因此是所有后续知识点的基础,下一章再慢慢来谈。
如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~

真不错 应该出本书了
感谢认可~
最好能直接编译
都是可以直接编译的,有问题可以加我Q。
根据代码书写,php编译后没有生成so文件