第一個參數是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.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
若是想獲取已有的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節。指針
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操做。
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將做不一樣處理:
該函數會同時輸出出錯的文件和行號,相似這樣:
Fatal error: no memory in /home/wiki/zdj/ext/test.php on line 6