之前的文章中,函数在接收的参数和返回的类型上都比较简单,但是往往实际中所遇到的都更加复杂一些。这篇文章主要说一下如何在php扩展开发中接收来自于用户空间的参数,并且对这些参数的类型、个数等信息进行相应的检查。
1. 使用zend_parse_parameters()进行自动的类型转换
在php的扩展中,最容易的得到输入参数的方法就是使用zend_parse_parameters()函数。
对这个函数的调用的第一个参数总是:ZEND_NUM_ARGS() TSRMLS_CC. 这个参数返回一个int型的输入参数的数目。PHP_FUNCTION(sample_getlong) { long foo; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &foo) == FAILURE) { RETURN_NULL(); } php_printf("The integer value of the parameter you " "passed is: %ld\n", foo); RETURN_TRUE; }这里是l也就是long类型,所以相对应的提前声明了一个long foo参数,然后通过引用的方式把值传递了进来。 下面给出更加详细的参数与c语言中的类型的对应关系: b ------ zend_bool l ------- long d ------- double s ------- char* , int r ------- zval* a ------ zval* o ------ zval* O ----- zval*, zend_class_entry* z ------ zval* Z ----- zval**
function sample_hello_world($name) { echo "Hello $name!\n"; }在c语言中,要使用的就是zend_parse_parameters函数了:
PHP_FUNCTION(sample_hello_world) { char *name; int name_len; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &name_len) == FAILURE) { RETURN_NULL(); } php_printf("Hello "); PHPWRITE(name, name_len); php_printf("!\n"); }
function sample_hello_world($name, $greeting) { echo "Hello $greeting $name!\n"; } sample_hello_world('John Smith', 'Mr.');
Or:
PHP_FUNCTION(sample_hello_world) { char *name; int name_len; char *greeting; int greeting_len; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &name, &name_len, &greeting, &greeting_len) == FAILURE) { RETURN_NULL(); } php_printf("Hello "); PHPWRITE(greeting, greeting_len); php_printf(" "); PHPWRITE(name, name_len); php_printf("!\n"); }
function sample_hello_world($name, $greeting='Mr./Ms.') { echo "Hello $greeting $name!\n"; }这个时候在调用的时候,可以不提供第二个参数:
sample_hello_world('Ginger Rogers','Ms.'); sample_hello_world('Fred Astaire');
PHP_FUNCTION(sample_hello_world) { char *name; int name_len; char *greeting = "Mr./Mrs."; int greeting_len = sizeof("Mr./Mrs.") - 1;//给定默认值,找出默认的长度 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s", &name, &name_len, &greeting, &greeting_len) == FAILURE) {//特殊的元字符|立刻就用上了 RETURN_NULL(); } php_printf("Hello "); PHPWRITE(greeting, greeting_len); php_printf(" "); PHPWRITE(name, name_len); php_printf("!\n"); }
PHP_FUNCTION(sample_arg_fullnull) { zval *val; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &val) == FAILURE) { RETURN_NULL(); } if (Z_TYPE_P(val) == IS_NULL) {//使用zval检查为空的方式 val = php_sample_make_defaultval(TSRMLS_C); } ... PHP_FUNCTION(sample_arg_nullok) { zval *val; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z!", &val) == FAILURE) { RETURN_NULL(); } if (!val) {// c语言风格的检查为空的方式 val = php_sample_make_defaultval(TSRMLS_C); } ...
当一个变量传入函数的时候,不管是不是用传引用的方式,refcount总是至少为2.一个是本身,一个是传进函数的拷贝。在对这个zval进行更改之前,把它从一个非引用的集合中分离出来是很必要的。
使用/会很方便,它会自动的把任何copy-on-write引用(也就是假引用的)的变量分离出来。
这个特性跟NULL标志位一样,需要的时候才用到。
zend_get_parameters():
如果想要兼容老版本的php或只想以zval作为载体来接收参数,那么可以考虑使用zend_get_parameters()函数来接收参数
它与zend_parse_parameters()相比,直接获取,不做解析。不会自动进行类型转换,所有参数在扩展实现中的载体都是用zval的.
ZEND_FUNCTION(sample_onearg) {
zval *firstarg; if (zend_get_parameters(ZEND_NUM_ARGS(), 1, &firstarg)== FAILURE) { php_error_docref(NULL TSRMLS_CC, E_WARNING,"Expected at least 1 parameter."); RETURN_NULL(); } /* Do something with firstarg... */ }
ZEND_FUNCTION(sample_onearg) { zval **firstarg; if (zend_get_parameters_ex(1, &firstarg) == FAILURE) { WRONG_PARAM_COUNT;抛出一个E_WARNING级别的错误信息,并自动return。 } /*
还有两种zend_get_parameters_**函数,专门用来解决很多或者无法提前知道参数数目的情况。php语言中的var_dump()函数,可以输入任意数量的参数。
ZEND_FUNCTION(var_dump) { int i, argc = ZEND_NUM_ARGS(); zval ***args; args = (zval ***)safe_emalloc(argc, sizeof(zval **), 0); if (ZEND_NUM_ARGS() == 0 || zend_get_parameters_array_ex(argc, args) == FAILURE) { efree(args); WRONG_PARAM_COUNT; } for (i=0; i<br> 程序首先获取参数数量,然后通过safe_emalloc函数申请相应大小的内存来存储这些zval**的参数。这里使用zend_get_parameters_array_ex()函数来把传递给函数的参数填充到args中。提醒一下,还存在一个zend_get_parameters_array()函数,唯一不同是它将zval*类型的参数填充到args中,并且需要ZEND_NUM_ARGS()作为参数。<br> </p> <p><br> </p> <p><br> </p> <p><strong>2. Arg info参数和类型的绑定</strong></p> <p>这个arg info结构是ZE2才有的。每一个arg info声明都由一个ZEND_BEGIN_ARG_INFO()或ZEND_BEGIN_ARG_INFO_EX()宏组成,后面跟着0个或多个ZEND_ARG_*INFO(), 然后最后以ZEND_END_ARG_INFO()作为结尾。<br> 假定要重写count()函数:<br> </p> <p><pre class="code">PHP_FUNCTION(sample_count_array) { zval *arr; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &arr) == FAILURE) { RETURN_NULL(); } RETURN_LONG(zend_hash_num_elements(Z_ARRVAL_P(arr))); }
ZEND_BEGIN_ARG_INFO(php_sample_array_arginfo, 0) ZEND_ARG_ARRAY_INFO(0, "arr", 0) ZEND_END_ARG_INFO() 。。。 PHP_FE(sample_count_array, php_sample_array_arginfo) 。。。
而对于对象来说,也可以通过arg info进行限定:
ZEND_BEGIN_ARG_INFO(php_sample_class_arginfo, 0) ZEND_ARG_OBJECT_INFO(1, "obj", "stdClass", 0) ZEND_END_ARG_INFO()
这里第一个参数被设为1,表示是引用方式传递,但是对象其实在ZE2中都是引用传递的。不要忘记了array和object的allow_null选项。
如果使用的是php4的话,只能用PHP_TYPE_P()进行检查,或使用convert_to_type()方法进行类型转换。