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表示没有注册任何函数:

我们可以通过赋值该字段完成全局函数的注册。

在这里,我决定注册2个函数:my_strtolower和my_strtoupper,用于字符串大小写转换。因此,我们现在定义一个zend_function_entry数组如下:

它列举了myext扩展要导出的2个函数,这个数组同样以空元素结尾。

zend_function_entry定义在Zend/zend_API.h中:

fname是PHP函数名,handler是对应的C函数指针,arg_info是描述函数的参数信息,num_args描述了参数的个数,flags对于全局函数没有意义因此传0(对于类成员函数有意义,例如private,final等描述信息)。

以PHP函数myext_strtolower为例,我们传了zif_strtolower这个函数指针作为其实现:

实现函数的参数是Zend要求的,execute_data是执行函数时的各种信息,主要用途是获取传入的参数。return_value用作函数返回值,我们只需要把返回值填充进去即可。函数里面的实现代码稍后再作解释。

为什么函数名要以zif_开头呢?这个主要是Zend惯用习惯,表示Zend Internal Fcuntion,就是指通过C语言实现的PHP函数,与用户在PHP里实现的Function相对。绝大多数扩展会通过宏PHP_FUNCTION(名称)来定义zif函数:

最终的结果就是zif_函数名这样的样子,而本教程的原则是尽量避免使用宏(因为实在没有什么营养),以便展示出Zend的原始面目,更加便于大家理解Zend。

接着看一下myext_strtolower_arginfo/myext_strtoupper_arginfo这2个函数的参数描述信息是如何构造的:

以myext_strtolower_arginfo为例,它描述了myext_strtolower这个PHP函数的参数列表。

它必须是一个zend_internal_arg_info数组,每个元素代表一个参数,先来看一下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完成注册:

functions字段已经设置为funcs,这样我们的2个函数myext_strtolower和myext_strtoupper就注册成功了。

但是目前2个函数的实现还没有讲解,里面的内容我计划在下一章详细来说,在这里我们先看一下大概:

函数通过2个ZEND的宏,获取了参数的个数和参数数组,然后调用了一定自定义的C函数strcase_convert将第一个参数传入得到返回值,赋值给了return_value作为最终的函数返回值。

在PHP里我们可以这样验证:

它会打印出:

helloPHP2017

结语

本章你应该掌握:

  • 定义zif函数
  • 描述函数的参数信息
  • 注册函数

在下一章中,我们需要重点了解PHP7的zval设计。zval是Zend层的值类型,它表达了多种数据类型(整形,字符串,数组,对象…),因此是所有后续知识点的基础,下一章再慢慢来谈。

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~