之前的文章中,函數在接收的參數和返回的類型上都比較簡單,但是往往實際中所遇到的都更加複雜一些。這篇文章主要說一下如何在php擴充開發中接收來自於使用者空間的參數,並且對這些參數的類型、個數等資訊進行相應的檢查。
1. 使用zend_parse_parameters()進行自動的類型轉換
在php的擴充中,最容易的得到輸入參數的方法就是使用zend_parse_parameters()函數。
對這個函數的調用的第一個參數總是:ZEND_NUM_ARGS() TSRMLS_CC. 這個參數返回一個int型的輸入參數的數目。
第二個參數是format參數,是由字串類型組成,分別對應著不同的Zend Engine支援的類型。
中給出了format參數可能具有的類型:
而接下來的參數取決於之前所請求的類型。對於比較簡單的類型來說,這個參數一般都是取引用的基元,如下例所示:
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_booll ------- longd ------- doubles ------- char* , intr ------- zval*a ------ zval*o ------ zval*O ----- zval*, zend_class_entry*z ------ zval*Z ----- zval**
注意到對於複雜的類型採用的是簡單的zval*類型。這跟返回複雜類型的時候沒有RETURN_*的道理是一樣的。ZPP所做的事情是保證所接收到的zval*是正確的類型。如果必要的話,它也會執行隱式的轉換,比如把數組轉成stdClass對象。
對於s類型來說,它比較特殊,一個char*一個int,這個還是主要因為php裡面字串的特殊結構:
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");}
如果有多個參數的話,那麼zend_parse_parameters會從左至右去提取這些參數:
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");}
除了類型標識符之外,還有三個元字元來修改參數被處理的方式:| :如果看到它了,說明前面的參數都是必須的,後面的參數都是可選的! :如果接收了一個php語言中的null變數,則直接轉成C語言裡的NULL,而不是封裝成IS_NULL類型的zval
/ :如果傳遞過來的變數與別的變數共用一個zval,而且不是真引用,那就要強制分離,新zval的is_ref__gc = 0,refcount__gc = 1
可選的參數:php中可以給參數提供預設值:
function sample_hello_world($name, $greeting='Mr./Ms.') { echo "Hello $greeting $name!\n";}這個時候在調用的時候,可以不提供第二個參數:
sample_hello_world('Ginger Rogers','Ms.');sample_hello_world('Fred Astaire');
在C的解釋中,有類似的實現方式:
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");}
對於選擇性參數來說,除非指定一般是不會有值的,所以提供預設參數很重要。大部分的情況下是NULL/0
IS_NULL VS NULL:
每一個zval的類型,即使是最簡單的IS_NULL類型,都佔據一定的記憶體空間,同時也需要時間去申請和釋放它們。所以很多時候沒有必要使用這個類型,下面兩段代碼裡面給出了對比:
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); }...
Forced Seperation強制分離:
當一個變數傳入函數的時候,不管是不是用傳引用的方式,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... */}
同時,它在接收失敗的時候不會自己拋出錯誤,也不能處理具有預設值的參數,最後一點跟parse不同的地方在於它會自動的把所有符合copy-on-write的zval進行強制分離,產生一個嶄新的拷貝送到函數內部。
如果不需要這個功能可以用zend_get_parameters_ex()它的參數是zval**的
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()方法進行類型轉換。
http://www.bkjia.com/PHPjc/635059.htmlwww.bkjia.comtruehttp://www.bkjia.com/PHPjc/635059.htmlTechArticle之前的文章中,函數在接收的參數和返回的類型上都比較簡單,但是往往實際中所遇到的都更加複雜一些。這篇文章主要說一下如何在php擴...