首頁  >  文章  >  後端開發  >  php擴充之擴充函數的傳參以及取得參數的實例詳解

php擴充之擴充函數的傳參以及取得參數的實例詳解

黄舟
黄舟原創
2017-08-14 09:37:492030瀏覽

前言

經過上一文php擴展之擴展框架的自動生成,對php擴展框架的整體了解,基本上可以說,對於扯淡如何寫php擴展和關鍵點有了一定的把握,但關鍵的還是在於如何寫PHP_FUNCTION的函數。
本文主要記錄一下,php在呼叫擴充的時候進行傳參,那麼擴充函數是怎麼接招的。當作自己的備忘錄

正文

1.zend_parse_parameters

獲取函數傳遞的參數,可以使用zend_parse_parameters函數,細心的同學會發現官方產生的預設的函數也是用這個函數來接收參數的。

這個函數怎麼用?

首先可以把這篇看書當作php的scanf一樣使用。 (這個函數不熟悉的自覺這裡)

zend_parse_parameters(int num_args TSRMLS_DC, char *type_spec, &参数1,&参数2…);

第一個參數是傳遞給函數的參數個數。通常的做法是傳給它ZEND_NUM_ARGS()。 (ZEND_NUM_ARGS() 來表示對傳入的參數「有多少要多少」)這是一個表示傳遞給函數參數總個數的巨集。

第二個參數是為了執行緒安全,總是傳遞TSRMLS_CC巨集。

第三個參數是一個字串,指定了函數期望的參數類型,後面緊跟著需要隨參數值更新的變數清單。由於PHP是弱型別語言,採用鬆散的變數定義和動態的型別判斷,而c語言是強型別的,而zend_parse_parameters()就是為了把不同型別的參數轉換為期望的型別。 (當實際的值無法強制轉成期望的類型的時候,會發出一個警告)

第四第五直到第n個參數,都是要傳進來的值的數值。

第三個參數詳解

關於第三個參數,這裡提供一個供選擇的參數清單:

##類型指定符對應的C型別描述#llong符號整數ddouble#sb#rao# #Ozval *指定類型的物件。需要提供目標物件的類別類型zzval *無任何操作的zval##這邊有兩點需要特別注意1.這裡的字串型對應的c型別有兩個參數。是的。這說明在使用zend_parse_parameters()函數的時候需要這麼使用:
zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s",&name, &name_len)
##char *, int 二進位字串,長度
#zend_bool 邏輯型式(1或0)
zval * 資源(檔案指針,資料庫連接等)
zval * 聯合數組
zval * 任何類型的物件
一個表示字串內容,另一個是字串長度。


2.需要大概知道一下,zval是啥?

zval是Zend引擎的值容器。無論這個變數是布林型,字串型或其他任何類型,其資訊總是會包含在一個zval聯合體中。來一波zval的結構:

typedef union _zval {    long lval;    double dval;    struct {        char *val;        int len;

    } str;

    HashTable *ht;

    zend_object_value obj;

} zval;

ps:關於typedef就不解釋了,因為我發現有人已經寫的非常好了,請點擊關於typedef的用法總結


這邊還牽涉到額外的三個用法,來增強我們接收參數的能力:

#符號##解釋#|它之前的參數都是必須的,之後的都是非必須的,也就是有預設值的。 !如果接收了一個PHP語言裡的null變量,則直接將其轉換成C語言裡的NULL,而不是封裝成IS_NULL類型的zval 。 /如果傳遞過來的變數與別的變數共用一個zval,而且不是引用,則進行強制分離,新的zval的is_ref__gc==0,和 refcount__gc==1.#

“|”和”!”将在下文有具体例子讲解,关于”/”虽然大概懂意思,但没想到具体的例子。

走一波与php的交互

正常的样子

在PHP中

<?phpfunction my_function($msg) {
    echo "我收到参数啦: {$msg}!\n";
}
my_function(&#39;咖啡色的羊驼&#39;);

如果my_function写成PHP扩展:

ZEND_FUNCTION(my_function) {
    char *msg;
    int msg_len;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s",&msg, &msg_len) == FAILURE){
        RETURN_NULL();
    }
    php_printf("我收到参数啦:");
    PHPWRITE(msg, msg_len);
    php_printf("!\n");
}

两个参数的样子

在PHP中

<?phpfunction my_function($email, $msg) {
    echo "我收到参数啦: {$email}、 {$msg}!\n";
}
my_function(&#39;123456@qq.com&#39;, &#39;咖啡色的羊驼&#39;);

如果my_function写成PHP扩展:

ZEND_FUNCTION(my_function) {
    char *email;
    int email_len;
    char *msg;
    int msg_len;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss",&msg, &msg_len,&email, &email_len) == FAILURE){
        RETURN_NULL();
    }
    php_printf("我收到参数啦:");
    PHPWRITE(email, email_len);
    PHPWRITE(msg, msg_len);
    php_printf("!\n");
}

两个参数,其中一个可选且有默认值

<?phpfunction my_function($email, $msg = &#39;咖啡色的羊驼&#39;) {
    echo "我收到参数啦: {$email}、 {$msg}!\n";
}
my_function(&#39;123456@qq.com&#39;);

如果my_function写成PHP扩展:

ZEND_FUNCTION(my_function) {
    char *email;
    int email_len;
    char *msg = "咖啡色的羊驼";
    int msg_len = sizeof("咖啡色的羊驼") - 1;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s",&msg, &msg_len,&email, &email_len) == FAILURE){
        RETURN_NULL();
    }
    php_printf("我收到参数啦:");
    PHPWRITE(email, email_len);
    PHPWRITE(msg, msg_len);
    php_printf("!\n");
}

这里说明了”|”的使用

参数为null时,省内存的写法

先来不省的写法

ZEND_FUNCTION(my_function) {
    zval *val;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z",&val) == FAILURE) {
        RETURN_NULL();
    }
}

省内存

ZEND_FUNCTION(my_function) {
    zval *val;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z!",&val) == FAILURE) {
        RETURN_NULL();
    }
}

这里例子用上了”!”,每个zval,包括IS_NULL型的zval,都需要占用一定的内存空间,需要cpu的计算资源来为它申请内存、初始化,并在它们完成工作后释放掉。但是很多代码都都没有意识到这一点。有很多代码都会把一个null型的值包裹成zval的IS_NULL类型,在扩展开发里这种操作是可以优化的,我们可以把参数接收成C语言里的NULL。
所以就差了一个!,第二个例子就更省了内存。

2.zend_get_arguments()

用法

例子是最好的用法讲解,上例子:

ZEND_FUNCTION(my_function) {
    zval *email;    if (zend_get_parameters(ZEND_NUM_ARGS(), 1, &email)== FAILURE) {
        php_error_docref(NULL TSRMLS_CC, E_WARNING,"至少需要一个参数");
        RETURN_NULL();
    }
    // ... }

区别与特点

1.能够兼容老版本的PHP,并且只以zval为载体来接收参数。
2.直接获取,而不做解析,不会进行类型转换,所有的参数在扩展实现中的载体都需要是zval类型的。
3.接受失败的时候,不会自己抛出错误,也不能方便的处理有默认值的参数。
4.会自动的把所有符合copy-on-write的zval进行强制分离,生成一个崭新的copy送到函数内部。

综合评价:还是用zend_parse_parameters吧,这个函数了解下即可,不给力。

3.zend_get_parameters_ex()

用法

ZEND_FUNCTION(my_function) {
    zval **msg;    if (zend_get_parameters_ex(1, &msg) == FAILURE) {
        php_error_docref(NULL TSRMLS_CC, E_WARNING,"至少需要一个参数");
        RETURN_NULL();
    }
    // ...}

不需要ZEND_NUM_ARGS()作为参数,因为它是在是在后期加入的,那个参数已经不再需要了。

区别与特点

1.此函数基本同zend_get_parameters()。
2.唯一不同的是它不会自动的把所有符合copy-on-write的zval进行强制分离,会用到老的zval的特性

综合评价:极端情况下可能会用到,这个函数了解下即可。

zend_get_parameter_**

这个包括:zend_get_parameters_array_ex()和zend_get_parameters_array()

用法

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<argc; i++) {
        php_var_dump(args[i], 1 TSRMLS_CC);
    }
    efree(args);
}

这个有点复杂,需要解释一下:
程序首先获取参数数量,然后通过safe_emalloc函数申请了相应大小的内存来存放这些zval**类型的参数。这里使用了zend_get_parameters_array_ex()函数来把传递给函数的参数填充到args中。
是的
这个参数专门用于解决像php里面的var_dump的一样,可以无限传参数进去的函数的实现

区别与特点

1.用于应对无限参数的扩展函数的实现。
2.zend_get_parameters_array与zend_get_parameters_array_ex唯一不同的是它将zval*类型的参数填充到args中,并且需要ZEND_NUM_ARGS()作为参数。

综合评价:当遇到确实需要处理无限参数的时候,真的要用这个函数了。zend_parse_parameters真的做不到啊~

总结

抛开一切,最少也要学会zend_parse_parameters()的用法。好的。扩展函数传参数的技能get了。

以上是php擴充之擴充函數的傳參以及取得參數的實例詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn