深入PHP内核之参数

1、看一下一个扩展中的简单代码

ZEND_BEGIN_ARG_INFO(params_add_arginfo, 0)
    ZEND_ARG_INFO(0, a)
    ZEND_ARG_INFO(0, b)
ZEND_END_ARG_INFO()

PHP_FUNCTION(params_add) {
    long a,b;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ll", &a, &b) == FALSE) {
        return;
    }
    RETURN_LONG(a+b);
}

 2、参数相关宏的定义 (Zend/zend_API.h)

#define ZEND_ARG_INFO(pass_by_ref, name)
{ #name, sizeof(#name)-1, NULL, 0, 0, pass_by_ref, 0, 0 },
//声明普通参数,可以用来表示PHP中的int, float, double, string等基本数组类型

#define ZEND_ARG_PASS_INFO(pass_by_ref)
{ NULL, 0, NULL, 0, 0, pass_by_ref, 0, 0 },
//pass_by_ref为1时,强制设置后续的参数为引用类型 

#define ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null)
{ #name, sizeof(#name)-1, #classname, sizeof(#classname)-1, IS_OBJECT, pass_by_ref, allow_null, 0 },
//声明对象类型的参数

#define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null)
 { #name, sizeof(#name)-1, NULL, 0, IS_ARRAY, pass_by_ref, allow_null, 0 },
//声明数组类型的参数

#define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null)
 { #name, sizeof(#name)-1, NULL, 0, type_hint, pass_by_ref, allow_null, 0 },

#define ZEND_ARG_VARIADIC_INFO(pass_by_ref, name)
{ #name, sizeof(#name)-1, NULL, 0, 0, pass_by_ref, 0, 1 },

#define ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args)  
    static const zend_arg_info name[] = {                                                                       
        { NULL, 0, NULL, required_num_args, 0, return_reference, 0, 0 },
#define ZEND_BEGIN_ARG_INFO(name, _unused)  
    ZEND_BEGIN_ARG_INFO_EX(name, 0, ZEND_RETURN_VALUE, -1)
#define ZEND_END_ARG_INFO()     };

 3、展开ZEND_BEGIN_ARG_INFO语句

static const zend_arg_info params_add_arginfo[] = {
    {NULL, 0, NULL, -1, 0, ZEND_RETURN_VALUE, 0},
    {a, sizeof(a)-1, NULL, 0, 0, 0, 0},
    {b, sizeof(b)-1, NULL, 0, 0, 0, 0},
};

 4、参数在zend中的定义(Zend/zend_compile.h)

typedef struct _zend_arg_info {
    const char *name; //参数的名称
    zend_uint name_len; //参数名称的长度
    char *class_name; //当参数类型为类时,指定类的名称
    zend_uint class_name_len; //类名称的长度
    zend_uchar type_hint; 
    zend_bool allow_null; //是否允许设置为null
    zend_bool pass_by_reference; //是否设置为引用,即使用&操作符
}


#define PHP_FUNCTION            ZEND_FUNCTION //函数的实现
#define ZEND_FUNCTION(name)             ZEND_NAMED_FUNCTION(ZEND_FN(name))
#define ZEND_NAMED_FUNCTION(name)       void name(INTERNAL_FUNCTION_PARAMETERS)
#define INTERNAL_FUNCTION_PARAMETERS 
int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC

 5、zend_parse_parameters的参数

Boolean         b zend_bool      //布尔值
Long            l long           //长整数
Double          d double         //双精度浮点数
String          s char *, int    //符串 (也可能是空字节)和其长度
Resource        r zval*          // 资源, 保存在 zval*
Array           a zval*          //数组, 保存在 zval*
Object          o zval*          //任何类的)对象, 保存在 zval*
zval            z zval*          //实际的 zval*
zval            O zval*         //由class entry 指定的类的)对象, 保存在 zval*
HashTable       h HashTable*     //数组的哈希表
Function       f char *, int     //函数,方法名 (版本 > php5.1)


|  -表明剩下的参数都是可选参数。如果用户没有传进来这些参数值,那么这些值就会被初始化成默认值。
/  -表明参数解析函数将会对剩下的参数以 SEPARATE_ZVAL_IF_NOT_REF() 的方式来提供这个参数的一份拷贝,除非这些参数是一个引用。
!  -表明剩下的参数允许被设定为 NULL(仅用在 a、o、O、r和z身上)。如果用户传进来了一个 NULL 值,则存储该参数的变量将会设置为 NULL。 

看看官方文档中提供的几个例子:

/* 取得一个长整数,一个字符串和它的长度,再取得一个 zval 值。 */  
long l;  
char *s;  
int s_len;  
zval *param;  

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lsz", &l, &s, &s_len, &param) == FAILURE) {  
return;  
}  

/* 取得一个由 my_ce 所指定的类的一个对象,另外再取得一个可选的双精度的浮点数。 */  
zval *obj;  
double d = 0.5;  
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O|d", &obj, my_ce, &d) == FAILURE) {  
return;  
}  

/* 取得一个对象或空值,再取得一个数组。如果传递进来一个空对象,则 obj 将被设置为 NULL。*/  
zval *obj;  
zval *arr;  
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O!a", &obj, &arr) == FAILURE) {  
return;  
}  

/* 取得一个分离过的数组。*/  
zval *arr;  
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a/", &arr) == FAILURE) {  
return;  
}  

/* 仅取得前 3 个参数(这对可变参数的函数很有用)。*/  
zval *z;  
zend_bool b;  
zval *r;  
if (zend_parse_parameters(3, "zbr!", &z, &b, &r) == FAILURE) {  
return;  
}  

 在接收参数时还有一个可用的函数zend_parse_parameters_ex,允许我们传入一些flags来控制解析参数的动作,使用方式如下所示:

int zend_parse_parameters_ex(int flags, int num_args TSRMLS_DC, char *type_spec, ...); 

 目前flags仅能传入ZEND_PARSE_PARAMS_QUIET这个值,表示函数不输出任何错误信息,如下面的示例:

long l1, l2, l3;  
char *s;  

if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET,  
                        ZEND_NUM_ARGS() TSRMLS_CC,  
                        "lll", &l1, &l2, &l3) == SUCCESS) {  
/* manipulate longs */  
} else if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET,  
                               ZEND_NUM_ARGS(), "s", &s, &s_len) == SUCCESS) {  
/* manipulate string */  
} else {  
php_error(E_WARNING, "%s() takes either three long values or a string as argument",  
         get_active_function_name(TSRMLS_C));  
return;  
}  

 可变参数

由于PHP支持可变参数,所以在接收可变参数时,使用前面介绍的两个方法就不太合适,我们可以用zend_get_parameters_array_ex()来代替,如下面的示例:

zval **parameter_array[4];  

/* 取得参数个数 */  
argument_count = ZEND_NUM_ARGS();  

/* 看一下参数个数是否满足我们的要求:最少 2 个,最多 4个。 */  
if(argument_count < 2 || argument_count > 4)  
{  
WRONG_PARAM_COUNT;  
}  

/* 参数个数正确,开始接收。 */  
if(zend_get_parameters_array_ex(argument_count, parameter_array) != SUCCESS)  
{  
WRONG_PARAM_COUNT;  
}  

 6、获取参数数量

PHP无法根据函数的显式声明来对调用进行语法检查,而且它还支持可变参数,所以我们就不得不在所调用函数的内部来获取参数个数。我们可以使用宏ZEND_NUM_ARGS来获取参数个数,如下面的代码:

if(ZEND_NUM_ARGS() != 2)
{
	WRONG_PARAM_COUNT
}

 这段代码使用宏WRONG_PARAM_COUNT抛出一个参数个数错误

7、更多的参数类型

#define Z_TYPE_P //获取参数类型 
//IS_NULL|IS_BOOL|IS_LONG|IS_DOUBLE|IS_STRING|IS_ARRAY|IS_RESOURCE|IS_OBJECT
#define Z_BVAL_P //获取bool类型参数的值
#define Z_LVAL_P //获取long类型参数的值
#define Z_DVAL_P //获取float类型参数的值
#define Z_STRVAL_P //获取string类型参数的值
#define Z_STRLEN_P //获取string类型参数的长度
#define Z_ARRVAL_P //获取array类型参数的值
#define Z_RESVAL_P //获取resource类型参数的值
#define Z_OBJCE_P //获取object类型参数的值
原文地址:https://www.cnblogs.com/chenpingzhao/p/4833917.html