PHP內核介紹及擴展開發指南—Extensions 的編寫(下)

第一個參數是HashTable,在1.2.3節提到Zend使用HashTable來存儲PHP函數,function_table用於指 定從哪一個HashTable中獲取函數。一般應該用CG(function_table),展開就是 compiler_globals.function_table,compiler_globals是一個用來存儲編譯器數據的全局數據結構(與其對應 的還有個EG宏,即executor_globals,它用來存儲執行器數據)。compiler_globals.function_table裏面存 儲了全部咱們能夠在PHP頁面裏面調用的函數,包括Zend內建函數、PHP標準庫函數、模塊導出的函數以及用戶使用PHP代碼定義的函數。
object_pp是一個對象,當指定該值時,Zend會從對象的函數表中獲取函數,這裏不予討論,老是設爲NULL。
function_name 必須是string型的zval,存儲咱們但願調用的函數的名稱。爲何使用zval而不是直接用char*,是由於Zend考慮到大部分狀況下,咱們都 是從用戶那得到參數,而後再調用call_user_function_ex的,這樣就能夠不做處理直接把用戶參數傳給該函數。固然,咱們也能夠手動建立 一個string型zval傳給它。
retval_ptr_ptr用於獲取函數的返回值,Zend執行完指定的函數後,它就將返回值的指針填充到這裏。
param_count和params用於指定函數的參數,params是個zval **這點可能讓人感到奇怪,但考慮到該函數的常見用法(見下面的示例)以及2.2.2節關於函數參數的介紹,就一點也不奇怪了。
no_separation用於指定是否在必要時執行zval分離(參見1.1.3),這在寫入非引用zval時發生。應該老是將其設爲0,表示執行zval分離,不然可能破壞數據。
symbol_table用於指定目標函數的active_symbol_table(參見1.2.3),一般應該使用NULL,這樣Zend會爲目標函數生成一個空的符號表。
說了這麼多,該動動手了,下面的程序片斷簡單實現了PHP API call_user_func的功能:php

雙擊代碼全選
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
ZEND_FUNCTION(call)
{
     int num_args = ZEND_NUM_ARGS();
     if (num_args < 1)
         WRONG_PARAM_COUNT;
     zval ***args = (zval***)emalloc(sizeof(zval**)*num_args);
zval *ret_zval;
// 獲取傳入的參數
if (zend_get_parameters_array_ex(num_args, args TSRMLS_CC)
== FAILURE)
     {
         efree(args);
         return ;
}
// 第一個參數做爲函數名,後面的做爲函數參數
if (call_user_function_ex(CG(function_table), NULL, **args,
     &ret_zval, num_args - 1, args + 1, 0, NULL TSRMLS_CC)
== FAILURE)
     {
         efree(args);
         zend_error(E_ERROR, "Function call failed" );
}
// 將函數返回值反饋給用戶
     *return_value = *ret_zval;
     efree(args);
}
 

1.6訪問PHP變量

1.6.1  設置

1.2.3節提到Zend使用HashTable來存儲全局和局部變量符號,所以訪問PHP變量,其實就是操做HashTable。固然,咱們不須要手工去作,Zend提供了一組宏完成這些工做。
PHP變量的建立共有三步,首先須要建立一個zval結構,可以使用以下的宏:瀏覽器

雙擊代碼全選
1
MAKE_STD_ZVAL(zval*)
 

這個宏先調用emalloc分配一塊zval,而後將其refcount設爲一、is_ref設爲0。
以後就是設置zval的值,一樣,咱們不須要直接操做zval的成員,Zend已經提供了以下的宏:

可能你會發現,這個表格和2.3節裏面的返回值宏表格很類似,不錯,返回值宏就是直接調用的ZVAL_xxx。
既然有了zval,下面把它添加到變量符號表裏就能夠了,可使用以下的一組宏:數據結構

 
1
2
ZEND_SET_SYMBOL(symtable, name, var )
ZEND_SET_GLOBAL_VAR(name, var )
 

symtable 用來指定你想插入的符號表,通常使用EG(active_symbol_table),表示訪問當前調用者的活動符號表。若是想強制訪問全局符號表,能夠 用&EG(symbol_table),這也正是ZEND_SET_GLOBAL_VAR(name, var)所作的。這兩個宏的最終效果和執行PHP賦值語句name = var徹底同樣。
若是隻是訪問全局變量,可使用單個宏代替上述三步:函數

 
1
2
3
4
SET_VAR_STRING(name, value)
SET_VAR_STRINGL(name, value, length)
SET_VAR_LONG(name, value)
SET_VAR_DOUBLE(name, value)
 

上述宏分別用於建立全局的string、long和double變量,它們在內部執行了以上三步,固然,最後調用的是ZEND_SET_GLOBAL_VAR宏。ui

1.6.2 獲取

若是想獲取已有的PHP變量,則只能直接訪問HashTable,Zend並無提供相應的操做:spa

 
1
2
3
4
int zend_hash_find(
HashTable *ht,
char *arKey, uint nKeyLength,
void **pData)
 

這個函數從HashTable中查找元素,pData用於獲取結果值,Bucket.pData將被放到這裏(若是找到的話)。函數成功則返回SUCCESS,不然返回FAILURE。
下面是個示例:設計

 
1
2
3
4
5
6
zval **ppzval; // Bucket.pData裏存放的是zval**
if (zend_hash_find(EG(active_symbol_table), "var" , 4,
(void**)&ppzval) == SUCCESS)
printf( "var.refcount = %dn" , (*p)->refcount);
     else
         printf( "Not Foundn" );
 

這段代碼從活動符號表中查找名爲var的變量,須要注意的是nKeyLength是4,必須包括結尾的0。
得到變量後,拿來讀是沒有問題的,可是寫操做就應該當心對待了。只有當refcount爲1或者is_ref爲1,才能夠寫入;不然應該進行zval分離,具體參見1.2.3節。指針

1.6.3  常量

PHP常量的內部定義以下:code

雙擊代碼全選
1
2
3
4
5
6
7
typedef struct _zend_constant {
     zval value;
     int flags;
     char *name;
     uint name_len;
     int module_number;
} zend_constant;
 

常 量的值依然使用zval存儲,但這裏的zval是私有的,不會和其餘變量或常量共享,其refcount和is_ref被忽略。 module_number是模塊號,在啓動函數中能夠獲取該值(參見2.4),當模塊被卸載時,Zend會使用模塊號查找和刪除全部該模塊註冊的常量。 若是但願在模塊被卸載後,常量依然有效,能夠將module_number設爲0。另外一個注意點是,name_len須要包含結尾的0。
flags值能夠是以下兩個,可使用」|」聯用:

HashTable裏,其key是常量名稱,value是zend_constant,注意不是zend_constant*,所以HashTable會複製一份zend_constant做爲value。
獲取一個常量很是簡單,只要傳遞常量名和接受常量值的zval:orm

 
1
2
int zend_get_constant(char *name, uint name_len, zval *result
     TSRMLS_DC);
 

設置常量稍微複雜一點,須要先填寫一個zend_constant結構,要注意的是,常量只能是long、double和string。而後使用以下函數將其加入常量表:
同時,Zend也爲咱們提供了以下的宏,能夠直接建立常量:

 
1
int zend_register_constant(zend_constant *c TSRMLS_DC);
 


上 述宏的MAIN版本用於建立module_number爲0的宏,在模塊被卸載後,常量依然有效。而非MAIN版本則假設存在一個名爲 module_number的int變量,並拿來給zend_constant.module_number賦值,可見這組宏本來就是爲在模塊啓動函數裏 調用而設計的。另外,當建立string型常量時,Zend也會dup一份字符串,所以能夠直接使用C串指定常量值。
最後須要指出的是,上述函數和宏都沒法改變已有的常量,若是發現已經存在同名常量,則函數失敗。若是想修改的話,只能經過HashTable操做。

1.7輸出信息

Zend提供了兩個函數用於向瀏覽器輸出信息:

 
1
2
int zend_printf( const char *format, ...);
void zend_error(int type, const char *format, ...);
 

zend_printf用法和C的printf同樣;zend_error用於輸出錯誤信息,type能夠指定錯誤的性質,對於不一樣的錯誤,Zend將做不一樣處理:
該函數會同時輸出出錯的文件和行號,相似這樣:

1 Fatal error: no memory in /home/wiki/zdj/ext/test.php on line 6
相關文章
相關標籤/搜索