程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 網頁編程 >> PHP編程 >> 關於PHP編程 >> php擴展與嵌入--php擴展的參數

php擴展與嵌入--php擴展的參數

編輯:關於PHP編程

之前的文章中,函數在接收的參數和返回的類型上都比較簡單,但是往往實際中所遇到的都更加復雜一些。這篇文章主要說一下如何在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_bool l ------- long d ------- double s ------- char* , int r ------- 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()方法進行類型轉換。









  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved