Linux下PHP7扩展开发入门教程2:代码结构详解

上一节简单的演示了一遍PHP7扩展开发和编译安装的流程,本文对上一节中的函数例子进行详细解释,帮助理解扩展开发。

开始之前

如果不熟悉扩展开发的流程,请先浏览: Linux下PHP7扩展开发入门教程1-扩展开发流程

代码结构详解

编写扩展中的函数,一般情况下,函数结构如下:

1
2
3
4
5
6
7
8
9
10
/* {{{ 注释中写函数原型,包括函数名、参数、返回值
*/
PHP_FUNCTION(函数名)
{
// 1. 定义变量类型
// 2. 定义参数类型
// 3. 实际处理逻辑
// 4. 返回值(如果有)
}
/*}}}*/

下面是生成扩展开发的骨架时的第二个函数例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 以下注释是函数的原型,即:函数learn_ext_test2有一个string类型的可选参数,返回值也是string。
/* {{{ string learn_ext_test2( [ string $var ] ) /
*/
PHP_FUNCTION(learn_ext_test2) // 定义一个php函数,函数名为learn_ext_test2
{
/**
* 定义一个字符串 var,如果调用函数时没有传入参数,则默认值是World
* 如果调用函数时传入了参数,则var会被修改为实参的值
*/
char *var = "World";
// 字符串长度,同理,如果调用函数时没有传入参数,长度就是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);// retval为:“Hello” + $var

RETURN_STR(retval); // 返回retval
}
/* }}}*/

如果对C语言不熟悉的话,小朋友们可能会有很多问号:

  1. 下面这段代码是什么语法?为什么后面没有分号?

    1
    2
    3
    4
    5
    6
    ZEND_PARSE_PARAMETERS_START(0, 1)       
    Z_PARAM_OPTIONAL
    Z_PARAM_STRING(var, var_len)
    ZEND_PARSE_PARAMETERS_END();
    // .....
    RETURN_STR(retval);

    :上面这些大写的“函数”其实是C语言中的宏定义,编译之前会进行“宏替换”,即把它们替换成相应的C语言代码。

    在编译时,ZEND_PARSE_PARAMETERS_START会被替换成检查参数个数的代码,如果调用php函数时参数个数不对,则会抛出异常。

    如果感兴趣,可以查看一下宏定义,ZEND_PARSE_PARAMETERS_START的定义在Zend/zend_API.h :1139,不同php版本代码位置会有差异。

  2. ZEND_PARSE_PARAMETERS_START(0, 1)的含义是什么?其中的参数0和1是什么意思?
    ZEND_PARSE_PARAMETERS_START有两个参数,它们分别是参数个数的最小值和最大值。因为编写的php函数只有1个参数并且是可选的,所以参数的个数是0~1。

  3. 为什么Z_PARAM_STRING(var, var_len)要传参数var_len进去?
    Z_PARAM_STRING也是一个宏定义,它有两个参数,第一个参数var是一个char类型的指针。
    调用php函数时,如果没有传入参数,它的就是默认值”World”;如果传入了参数,var就会被修改为实际传入的字符串。同理,如果传入了参数,var_len也会被修改为实参的长度。

  4. 为什么最后函数返回是RETURN_STR(retval),而不是return retval?
    :跟ZEND_PARSE_PARAMETERS_START类似,RETURN_STR仍然是一个宏定义,最后代码会被替换为:
    1) 把retval转换为字符串;
    2) return retval

  5. 为什么在其它教程中,获取参数的写法不一样?
    :这是PHP7的新写法,叫做FAST_ZPP。以count函数为例,PHP7之前的写法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    PHP_FUNCTION(count)
    {
    zval *array;
    long mode = COUNT_NORMAL;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|l", &array, &mode) == FAILURE) {
    return;
    }
    ....
    }

    PHP7的FAST_ZPP写法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    PHP_FUNCTION(count)
    {
    zval *array;
    zend_long mode = COUNT_NORMAL;

    ZEND_PARSE_PARAMETERS_START(1, 2)
    Z_PARAM_ZVAL(array)
    Z_PARAM_OPTIONAL
    Z_PARAM_LONG(mode)
    ZEND_PARSE_PARAMETERS_END();
    ...
    }

    PHP7之前的语法依然可用,新版语法更加易于理解,且性能更高。

参数信息

写完函数后,还要写函数的参数信息ZEND_ARG_INFO,指明参数是否为引用,参数信息可以用于参数类型提示和反射等。

一般格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* {{{ arginfo
*/
// 扩展中的第1个函数,假设函数为:test_1(a, b)
ZEND_BEGIN_ARG_INFO(arginfo_test_1, 0) // 第一个参数是“arginfo_函数名”,第二个参数没有用到,写0就行
ZEND_ARG_INFO(0, a) // ZEND_ARG_INFO的第一个参数表明参数a是否为引用类型,第二个参数是函数的参数名
ZEND_ARG_INFO(0, b) // 函数的第二个参数b
ZEND_END_ARG_INFO()

// 扩展中的第二个函数:test_2(arr)
ZEND_BEGIN_ARG_INFO(arginfo_test_2, 0)
ZEND_ARG_INFO(0, arr)
ZEND_END_ARG_INFO()
/* }}} */

// 扩展中的函数列表
/* {{{ array_util_functions[]
*/
static const zend_function_entry array_util_functions[] = {
PHP_FE(test_1, arginfo_test_1)
PHP_FE(test_2, arginfo_test_2)
PHP_FE_END
};
/* }}} */

下一步

终于要开始写代码了!

进入第3篇:Linux下PHP7扩展开发入门教程3: 编写第一个函数

参考资料