程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 網頁編程 >> PHP編程 >> PHP綜合 >> PHP的資源數據類型

PHP的資源數據類型

編輯:PHP綜合

資源數據類型

迄今為止, 你都是工作在非常基礎的用戶空間數據類型上, 字符串, 數值, TRUE/FALSE等值. 即便上一章你已經開始接觸數組了, 但也只是收集這些基礎數據類型的數組.

復雜的結構體

現實世界中, 你通常需要在更加復雜的數據集合下工作, 通常涉及到晦澀的結構體指針. 一個常見的晦澀的結構體指針示例就是stdio的文件描述符, 即便是在C語言中也只是一個指針.

#include <stdio.h>  
int main(void)  
{  
    FILE *fd;  
    fd = fopen("/home/jdoe/.plan", "r");  
    fclose(fd);  
    return 0;  
}

stdio的文件描述符和其他多數文件描述符一致, 都像是一個書簽. 你擴展的調用應用僅需要在feof(), fread(), fwrite(), fclose()這樣的實現函數調用時傳遞這個值. 有時, 這個書簽必須是用戶空間代碼可訪問的; 因此, 就需要在標准的php變量或者說zval *中有表示它的方法.

這裡就需要一種新的數據類型. RESOURCE數據類型在zval *中存儲一個簡單的整型值, 使用作為已注冊資源的索引用來查找. 資源條目包含了資源索引所表示的內部數據類型, 以及存儲資源數據的指針等信息.

定義資源類型

為了使注冊的資源條目所包含的資源信息更加明確, 需要定義資源的類型. 首先在你的sample.c中已有的函數實現下增加下面的代碼片段

static int le_sample_descriptor;  
PHP_MINIT_FUNCTION(sample)  
{  
    le_sample_descriptor = zend_register_list_destructors_ex(  
                NULL, NULL, PHP_SAMPLE_DESCRIPTOR_RES_NAME,  
                module_number);  
    return SUCCESS;  
}

接下來, 滾動到你的代碼文件末尾, 修改sample_module_entry結構體, 將NULL, /* MINIT */一行替換為下面的內容. 就像你給這個結構中增加函數列表結構時一樣, 你需要確認在這一行末尾保留一個逗號.

PHP_MINIT(sample), /* MINIT */  

最後, 你需要在php_sample.h中定義PHP_SAMPLE_DESCRIPTOR_RES_NAME, 將下面的代碼放到你的其他常量定義下面:

#define PHP_SAMPLE_DESCRIPTOR_RES_NAME "File Descriptor"  

PHP_MINIT_FUNCTION()代表第1章"PHP生命周期"中介紹的4個特殊的啟動和終止操作中的第一個, 關於生命周期, 在第12章"啟動, 終止以及之間的幾個關鍵點"和第13章"INI設置"中還將深入討論.

這裡需要知道的非常重要的一點是MINIT函數在你的擴展第一次加載時執行一次, 它會在所有請求到達之前被執行. 這裡我們利用這個機會注冊了析構函數, 不過它們是NULL值, 不過在通過一個唯一整型ID足以知道一個資源類型時, 你很快就會修改它.

注冊資源

現在引擎已經知道了你要存儲一些資源數據, 是時候給用戶空間的代碼一種方式去產生實際的資源了. 要做到這一點, 需要如下重新實現fopen()命令:

PHP_FUNCTION(sample_fopen)  
{  
    FILE *fp;  
    char *filename, *mode;  
    int filename_len, mode_len;  
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss",  
                        &filename, &filename_len,  
                        &mode, &mode_len) == FAILURE) {  
        RETURN_NULL();  
    }  
    if (!filename_len || !mode_len) {  
        php_error_docref(NULL TSRMLS_CC, E_WARNING,  
                "Invalid filename or mode length");  
        RETURN_FALSE;  
    }  
    fp = fopen(filename, mode);  
    if (!fp) {  
        php_error_docref(NULL TSRMLS_CC, E_WARNING,  
                "Unable to open %s using mode %s",  
                filename, mode);  
        RETURN_FALSE;  
    }  
    ZEND_REGISTER_RESOURCE(return_value, fp,  
                                le_sample_descriptor);  
}

為了讓編譯器知道什麼是FILE *, 你需要包含stdio.h. 這可以放在sample.c中, 但是為了本章後面部分做准備, 我還是要求你放到php_sample.h中.

如果你對前面的章節付出了努力, 最後一行前面的所有內容都應該可以讀懂. 這一行代碼執行的任務是將fp指針存儲到資源的索引中, 將它和MINIT中定義的類型關聯起來, 並存儲一個可用於查找的key到return_value中.

如果需要存儲多於一個指針的值, 或者存儲一個直接量, 則必須新分配一段內存用來存儲數據, 接著將指向這段內存的指針注冊為資源.

譯注:

1. 資源數據類型的注冊實際上是在list_destructors(Zend/zend_list.c中定義的靜態全局變量HashTable)中插入一個新構建的zend_rsrc_list_dtors_entry結構體, 這個結構體描述了這個資源類型的信息.

2. 資源數據的注冊(ZEND_REGISTER_RESOURCE)實際上是在EG(regular_list)中使用zend_hash_next_free_element()得到下一個數值下標, 作為資源的ID, 並將傳入的資源指針(封裝為zend_rsrc_list_entry結構體)存儲到EG(regular_list)中這個下標對應的元素中.

3. EG(regular_list)的初始化是在請求初始化階段完成的, 通過跟蹤代碼, 可以看到其函數調用流程如下: php_request_startup(main/main.c) --> zend_active(Zend/zend.c) --> init_compiler(Zend/zend_compile.c) --> zend_init_rsrc_list(Zend/zend_list.c). 通過觀察zend_init_rsrc_list()函數可以看出EG(regular_list)的析構函數是list_entry_destructor(Zend/zend_list.c). 而list_entry_destructor()的邏輯是從list_destructors(上面第一步所述的靜態全局變量)中查找要釋放的資源對象類型的信息, 接著按照注冊資源類型時所指定的析構器進行析構.

4. 按照上面幾點, 可以很容易理清本章前面所述內容. 首先注冊一個資源類型, 這個資源類型中包含了諸如所屬模塊編號, 析構器句柄這樣的信息. 接著, 在創建具體的資源對象時, 將資源對象和資源類型做了一個關聯.

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