之前的文章中,函數在接收的參數和返回的類型上都比較簡單,但是往往實際中所遇到的都更加復雜一些。這篇文章主要說一下如何在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
程序首先獲取參數數量,然後通過safe_emalloc函數申請相應大小的內存來存儲這些zval**的參數。這裡使用zend_get_parameters_array_ex()函數來把傳遞給函數的參數填充到args中。提醒一下,還存在一個zend_get_parameters_array()函數,唯一不同是它將zval*類型的參數填充到args中,並且需要ZEND_NUM_ARGS()作為參數。
2. Arg info參數和類型的綁定
這個arg info結構是ZE2才有的。每一個arg info聲明都由一個ZEND_BEGIN_ARG_INFO()或ZEND_BEGIN_ARG_INFO_EX()宏組成,後面跟著0個或多個ZEND_ARG_*INFO(), 然後最後以ZEND_END_ARG_INFO()作為結尾。
假定要重寫count()函數:
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_parse_parameters()會確保輸入到你函數中的參數是一個數組。但是如果你需要用zend_get_parameter()的話,那麼就需要自己在函數內部內建類型檢查。除非使用類型綁定:
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) 。。。
通過這種方式,zend engine就會幫你進行類型檢查了。同時還給了參數一個名字,從而使得產生的錯誤信息更加具有可讀性。
而對於對象來說,也可以通過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()方法進行類型轉換。