---
title: PHP扩展开发(二)-函数
---

> 弄好骨架之后，我们得给我们的扩展增加些 php 能够调用的函数，这里我们使用 vscode 进行开发

## 开发环境

> 给我们的 vscode 装好扩展，然后配置一下 include 路径

![](img/02-%E5%87%BD%E6%95%B0.assets/2018-09-21-14-20-32-%E7%9A%84%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE-300x132.png)

点击，生成一个配置文件，我的配置如下，php 的路径看自己的来定，我这里是宝塔安装的，路径为:**/www/server/php/72/include/php**，主要是自动提示

```json
{
  "configurations": [
    {
      "name": "Linux",
      "includePath": [
        "${workspaceFolder}/**",
        "/www/server/php/72/include/php",
        "/www/server/php/72/include/php/main",
        "/www/server/php/72/include/php/Zend",
        "/www/server/php/72/include/php/TSRM"
      ],
      "defines": [],
      "compilerPath": "/usr/bin/gcc",
      "cStandard": "c11",
      "cppStandard": "c++17",
      "intelliSenseMode": "clang-x64"
    }
  ],
  "version": 4
}
```

## 开发

### 返回和输出

> 假设我这里想添加一个输出一些东西的函数

这里用到了三个宏,**PHP_FUNCTION**,**RETURN\_\***,**PHP_FE**

php_printf 是 php 提供的一个输出函数

```c
PHP_FUNCTION(study_ext_print) {
	php_printf("我是输出到页面的内容\n");
	RETURN_STRING("学习PHP扩展开发~~");
}

/* }}} */
/* {{{ study_functions[]
 *
 * Every user visible function must have an entry in study_functions[].
 */
const zend_function_entry study_functions[] = {
	PHP_FE(confirm_study_compiled,	NULL)		/* For testing, remove later. */
	PHP_FE(study_ext_print, NULL)        /* 学习插件输出 */
	PHP_FE_END	/* Must be the last line in study_functions[] */
};
/* }}} */
```

然后再 make，sudo make install

#### 调试跑一次

修改我们目录下的`study.php`，在最后写一行,我们刚刚编写的这个函数

```php
echo study_ext_print()."\n";
```

输出，成功

```
huanl@huanl-CN15S:/www/server/php/72/src/ext/study$ php study.php
Functions available in the test extension:
confirm_study_compiled
study_ext_print

Congratulations! You have successfully modified ext/study/config.m4. Module study is now compiled into PHP.
我是输出到页面的内容
学习PHP扩展开发~~
```

#### 这里再说下这三个宏

##### PHP_FUNCTION

使用这个宏会将我们的函数最终定义成如下的形式

```cpp
void zif_study_ext_print(zend_execute_data *execute_data, zval *return_value)
```

~~官网上的是 php5.3 的版本，我这里是 php7，所以是这样，如果有什么错误，还望指出~~
然后因为这里的返回值是 void，所以在我们写函数的时候`return`不能带值

##### RETURN\_\*

这个宏一看就知道是 php 给我们的返回值，除了我上面所写的`RETURN_STR`外还有其他的类型

```c
#define RETURN_BOOL(b) 					{ RETVAL_BOOL(b); return; }
#define RETURN_NULL() 					{ RETVAL_NULL(); return;}
#define RETURN_LONG(l) 					{ RETVAL_LONG(l); return; }
#define RETURN_DOUBLE(d) 				{ RETVAL_DOUBLE(d); return; }
#define RETURN_STR(s) 					{ RETVAL_STR(s); return; }
#define RETURN_INTERNED_STR(s)			{ RETVAL_INTERNED_STR(s); return; }
#define RETURN_NEW_STR(s)				{ RETVAL_NEW_STR(s); return; }
#define RETURN_STR_COPY(s)				{ RETVAL_STR_COPY(s); return; }
#define RETURN_STRING(s) 				{ RETVAL_STRING(s); return; }
#define RETURN_STRINGL(s, l) 			{ RETVAL_STRINGL(s, l); return; }
#define RETURN_EMPTY_STRING() 			{ RETVAL_EMPTY_STRING(); return; }
#define RETURN_RES(r) 					{ RETVAL_RES(r); return; }
#define RETURN_ARR(r) 					{ RETVAL_ARR(r); return; }
#define RETURN_OBJ(r) 					{ RETVAL_OBJ(r); return; }
#define RETURN_ZVAL(zv, copy, dtor)		{ RETVAL_ZVAL(zv, copy, dtor); return; }
#define RETURN_FALSE  					{ RETVAL_FALSE; return; }
#define RETURN_TRUE   					{ RETVAL_TRUE; return; }
```

##### PHP_FE

这个宏帮助我们生成一个和 php 函数相关的结构体

```c
//宏如下，PHP_替换成了ZEND_
#define ZEND_FENTRY(zend_name, name, arg_info, flags)	{ #zend_name, name, arg_info, (uint32_t) (sizeof(arg_info)/sizeof(struct _zend_internal_arg_info)-1), flags },
#define ZEND_FE(name, arg_info)						ZEND_FENTRY(name, ZEND_FN(name), arg_info, 0)
//感觉就是帮我们省事了，不需要我们去重写结构体，上面那些结构体也是
typedef struct _zend_function_entry {
	const char *fname;//我们的php函数名
	zif_handler handler;//相当于再调用一次PHP_FUNCTION，c中函数的指针
	const struct _zend_internal_arg_info *arg_info;//参数的信息，就上一个函数来说，我们是NULL
	uint32_t num_args;//参数个数
	uint32_t flags;//flags这里是0
} zend_function_entry;
```

### 参数

> 假设来一个两数相加的函数，这时候我们就需要传送参数了
> 参考：[https://wiki.php.net/rfc/fast_zpp](https://wiki.php.net/rfc/fast_zpp)

我的 c 代码如下

```cpp
//定义参数结构
ZEND_BEGIN_ARG_INFO(add_param,0)
ZEND_ARG_INFO(0,num1)
ZEND_ARG_INFO(0,num2)
ZEND_END_ARG_INFO()

//函数
PHP_FUNCTION(study_add)
{
	long long num1=0,num2=0;
	if(zend_parse_parameters(ZEND_NUM_ARGS() , "ll", &num1,&num2)==FAILURE){
		RETURN_LONG(-1)
	}
	RETURN_LONG(num1+num2)
}

/* }}} */
/* {{{ study_functions[]
 *
 * Every user visible function must have an entry in study_functions[].
 */
const zend_function_entry study_functions[] = {
	PHP_FE(confirm_study_compiled,	NULL)		/* For testing, remove later. */
	PHP_FE(study_ext_print, NULL)        /* 学习插件输出 */
	PHP_FE(study_add, add_param)        /* 两数相加 */
	PHP_FE_END	/* Must be the last line in study_functions[] */
};
```

```php
echo "study_add:".study_add(10,123456);
```

#### 宏

##### ZEND_BEGIN_ARG_INFO，ZEND_ARG_INFO，ZEND_END_ARG_INFO

定义参数,我把源码贴上来

```cpp
#define ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args)	\
	static const zend_internal_arg_info name[] = { \
		{ (const char*)(zend_uintptr_t)(required_num_args), 0, return_reference, 0 },
#define ZEND_BEGIN_ARG_INFO(name, _unused)	\
	ZEND_BEGIN_ARG_INFO_EX(name, 0, ZEND_RETURN_VALUE, -1)
#define ZEND_END_ARG_INFO()		};

#define ZEND_ARG_INFO(pass_by_ref, name)                             { #name, 0, pass_by_ref, 0},
```

那么通过这些宏的转换，变成了这样

```cpp
static const zend_internal_arg_info add_param[] = {
	{ (const char*)(zend_uintptr_t)(-1), 0, 0, 0 },
	{ "num1", 0, 0, 0},
	{ "num2", 0, 0, 0},
};
```

#### zend_parse_parameters

获取参数

```cpp
//声明
ZEND_API int zend_parse_parameters(int num_args, const char *type_spec, ...);
```

第一个参数是我们要获取的参数的个数，第二个是 参数的格式化字符串，后面的是变量指针

| 数据类型 | 字符 | c 对应类型  |
| -------- | ---- | ----------- |
| Boolean  | b    | zend_bool   |
| Long     | l    | long        |
| Double   | d    | double      |
| String   | s    | char\*, int |
| Resource | r    | zval\*      |
| Array    | a    | zval\*      |
| Object   | o    | zval\*      |
| zval     | z    | zval\*      |

#### ZEND_NUM_ARGS

参数个数,一般这样填就好了
