php内核介绍及扩展开发指南 pdf vp进,PHP内核介绍及扩展开发指南—Extensions 的编写...
Extensions 的編寫
理解了這些運行機制以后,本章著手介紹Extensions 的編寫,但凡寫程序的人都知道hello world,那好,就從hello world開始。
1.1Hello World
這是摘自《PHP手冊》的示例程序:
/*?include?standard?header?*/
#include?"php.h"
/*?declaration?of?functions?to?be?exported?*/
ZEND_FUNCTION(first_module);
/*?compiled?function?list?so?Zend?knows?what's?in?this?module?*/
zend_function_entry?firstmod_functions[]?=
{
ZEND_FE(first_module,?NULL)
{NULL,?NULL,?NULL}
};
/*?compiled?module?information?*/
zend_module_entryfirstmod_module_entry=
{
STANDARD_MODULE_HEADER,
"First?Module",
firstmod_functions,
NULL,
NULL,
NULL,
NULL,
NULL,
NO_VERSION_YET,
STANDARD_MODULE_PROPERTIES
};
/*?implement?standard?"stub"?routine?to?introduce?ourselves?to?Zend?*/
#if?COMPILE_DL_FIRST_MODULE
ZEND_GET_MODULE(firstmod)
#endif
/*?implement?function?that?is?meant?to?be?made?available?to?PHP?*/
ZEND_FUNCTION(first_module)
{
long?parameter;
if?(zend_parse_parameters(ZEND_NUM_ARGS()?TSRMLS_CC,?"l",??meter)
==?FAILURE)
return;
RETURN_LONG(parameter);
}
這段代碼實現(xiàn)了一個簡單的extension,首先它包含了“php.h”,這是所有extensions都需要包含的頭文件,它定義、聲明了我們可以訪問的所有Zend數(shù)據(jù)結(jié)構(gòu)、常量和API等。下面對剩余的步驟進行解釋。
1.1.1? 聲明導(dǎo)出函數(shù)
ZEND_FUNCTION(first_module);
ZEND_FUNCTION宏用于聲明一個可在PHP代碼中調(diào)用的函數(shù),其參數(shù)即成為PHP函數(shù)名,因此,這一句聲明了一個名為first_module的PHP函數(shù),將其展開如下:
可見,ZEND_FUNCTION就是簡單的聲明了一個名為zif_ first_module的C函數(shù),zif可能是”Zend Internal Function”的縮寫。函數(shù)的原型滿足Zend引擎對PHP函數(shù)的調(diào)用約定,關(guān)于其參數(shù)將在后面章節(jié)進行解釋。
1.1.2? 聲明導(dǎo)出函數(shù)塊
聲明C函數(shù)后,Zend并不知道如何調(diào)用,我們需要使用如下的語句來完成C函數(shù)到PHP函數(shù)的映射:
zend_function_entry?firstmod_functions[]?=
{
ZEND_FE(first_module,?NULL)
{NULL,?NULL,?NULL}
};
這創(chuàng)建了一個zend_function_entry數(shù)組,zend_function_entry存儲了關(guān)于如何調(diào)用該PHP函數(shù)的信息,通過它Zend引擎就能夠理解和調(diào)用我們的函數(shù)。
其定義如下:
typedef?struct?_zend_function_entry?{
char?*fname;
void?(*handler)(INTERNAL_FUNCTION_PARAMETERS);
struct?_zend_arg_info?*arg_info;
zend_uint?num_args;
zend_uint?flags;
}?zend_function_entry;
fname是PHP函數(shù)名,是PHP代碼能夠通過它來調(diào)用我們的函數(shù);handler是指向我們在前面聲明的C函數(shù)的函數(shù)指針。這兩個參數(shù)已經(jīng)足以完成從C函數(shù)到PHP函數(shù)的映射。剩余的參數(shù)用于告訴Zend該PHP函數(shù)對于函數(shù)參數(shù)的要求,arg_info是個數(shù)組,它的每一項都描述了對應(yīng)下標的參數(shù),num_args是參數(shù)的個數(shù),具體將在后面的章節(jié)介紹。
我們可以手動填充一個zend_function_entry,但更好的辦法是使用Zend提供的宏ZEND_FE,因為Zend并不保證這個結(jié)構(gòu)以后不會變。ZEND_FE使用第一個參數(shù)作為PHP函數(shù)名,并且在添加了zif前綴后作為C函數(shù)名;第二個參數(shù)用于填充arg_info,通常使用NULL。上面的代碼將得到這樣一個zend_function_entry結(jié)構(gòu):{” first_module,”, zif_first_module, NULL, 0, 0}。當然,這并不是說PHP函數(shù)名必須和C函數(shù)名有什么關(guān)系,也可以通過宏ZEND_NAMED_FE來手動指定PHP函數(shù)名,不過這并不是個好主意。
我們必須為希望導(dǎo)出的每一個C函數(shù)都創(chuàng)建一個zend_function_entry結(jié)構(gòu),并將其放到一個數(shù)組中以備后用,數(shù)組最后一項的成員必須全部為NULL,這用于標記數(shù)組的結(jié)束。
1.1.3? 填寫模塊信息
下一步需要將我們的模塊介紹給Zend,主要包括我們的模塊名和導(dǎo)出的函數(shù),這通過填充一個zend_module_entry結(jié)構(gòu)來完成。
zend_module_entry firstmod_module_entry =
{
STANDARD_MODULE_HEADER,
"First Module",
firstmod_functions,
NULL,
NULL,
NULL,
NULL,
NULL,
NO_VERSION_YET,
STANDARD_MODULE_PROPERTIES
};
STANDARD_MODULE_HEADER和STANDARD_MODULE_
PROPERTIES宏填充了該結(jié)構(gòu)的首尾部分,具體填充了什么并不是我們需要關(guān)心的,并且為了兼容后續(xù)版本也最好不要手工修改。
第二、三項是模塊名稱和導(dǎo)出函數(shù),名稱可以任意填寫,導(dǎo)出函數(shù)就是我們在前面準備好的zend_function_entry數(shù)組。
接下來的五個參數(shù)是函數(shù)指針,其用法在后面介紹,這里只用NULL填充。
下面的參數(shù)是一個C字符串,用于表示模塊版本,如果沒有則使用NO_VERSION_YET,其實就是NULL。
填寫完畢后,需要把這個結(jié)構(gòu)傳給Zend引擎,這通過下面的語句完成:
#if?COMPILE_DL_FIRST_MODULE
ZEND_GET_MODULE(firstmod)
#endif
宏開關(guān)用于判斷是否是動態(tài)鏈接的,動態(tài)鏈接時才會執(zhí)行下面的語句,本文僅介紹動態(tài)鏈接的模塊,并不關(guān)心靜態(tài)鏈接時如何與Zend交流信息,因此,可以認為條件總為真。
ZEND_GET_MODULE(firstmod)最后展開得到名為get_module的一個函數(shù):
zend_module_entry?*get_module(void)
{
return?&firstmod_module_entry;
}
這個函數(shù)就是簡單的返回我們填充的zend_module_entry結(jié)構(gòu),這里需要注意的是結(jié)構(gòu)的名稱必須是xxx_module_entry,xxx是傳遞給ZEND_GET_MODULE的參數(shù)。當Zend加載我們的模塊時,它首先會解析并調(diào)用名為get_module的函數(shù),這樣就可以得到我們的zend_module_entry,于是,PHP代碼就可以調(diào)用模塊導(dǎo)出的函數(shù)了。
1.1.4 實現(xiàn)導(dǎo)出函數(shù)
代碼最后一部分實現(xiàn)了我們導(dǎo)出的函數(shù):
ZEND_FUNCTION(first_module)
{
long?parameter;
if?(zend_parse_parameters(ZEND_NUM_ARGS()?TSRMLS_CC,?"l",
?meter)?==?FAILURE)
return;
RETURN_LONG(parameter);
}
這里依然要用ZEND_FUNCTION來聲明函數(shù)原型,函數(shù)體通過Zend API和宏,訪問了函數(shù)參數(shù)并返回一個long值——這些都將在后面的章節(jié)進行詳細介紹。
1.2使用參數(shù)
函數(shù)的一個重要部分就是訪問參數(shù),但由于extension的特殊性,我們無法像通常的函數(shù)那樣來訪問參數(shù)。
先來看導(dǎo)出C函數(shù)的原型:
void?zif_first_module?(
int?ht,
zval?*?return_value,
zval?**return_value_ptr,
zval?*?this_ptr,
int?return_value_used
);
ht是用戶傳入?yún)?shù)的數(shù)目,但一般不應(yīng)直接讀取,而是通過宏ZEND_NUM_ARGS()來獲取,這通常用于判斷用戶是否傳入了規(guī)定數(shù)目的參數(shù)。下面介紹如何在我們的C函數(shù)中訪問這些參數(shù)。
1.2.1? 標準方法
常用的方法是使用下面這個函數(shù),其使用方法類似于scanf,采用格式化字符串和變長參數(shù)列表的方式:
int?zend_parse_parameters(int?num_args?TSRMLS_DC,?char?*type_spec,?...);
num_args指出我希望獲取的參數(shù)數(shù)目,通常使用ZEND_NUM_ARGS(),因為我們一般會先用ZEND_NUM_ARGS()判斷用戶是否傳入了規(guī)定數(shù)目的參數(shù)。TSRMLS_DC宏用于線程安全,define和declare時必須這樣填寫,在調(diào)用時應(yīng)該改用TSRMLS_CC。
type_spec是格式化字符串,其每個字符代表期望的當前參數(shù)的類型,之后應(yīng)傳遞相應(yīng)類型變量的指針來接收值,就像scanf那樣,可用的字符如下:
格式字符
PHP參數(shù)類型
接收變量類型
l
long
long
d
double
double
s
string
char*和int
b
boolean
zend_bool
r
resource
zval*
a
array
zval*
z
zval
zval*
o/O/C
類,不予討論
N/A
這里面,string是個特例,它需要兩個參數(shù),分別獲取字符串指針和長度,這是因為PHP沒有采用C串,不能根據(jù)0來判斷字符串結(jié)尾。下面是個示例程序:
//?獲取一個long、一個string和一個resource
long?l;
char?*s;????????//?字符串地址
int?s_len;??????//?字符串長度
zval?*res;
//?檢查參數(shù)數(shù)目
if(ZEND_NUM_ARGS()?!=?3)
WRONG_PARAM_COUNT;?//?該宏輸出相應(yīng)錯誤信息并退出當前函數(shù)
if?(zend_parse_parameters(ZEND_NUM_ARGS()?TSRMLS_CC,
"lsr",?&l,?&s,?&s_len,?&res)?==?FAILURE)
return;
由于PHP語法不能規(guī)定函數(shù)原型,因此用戶可以傳遞任意類型的參數(shù),對此,zend_parse_parameters自動進行了類型檢查和轉(zhuǎn)換:在內(nèi)置標量類型,即long、double、boolean和string之間,Zend會自動進行類型轉(zhuǎn)換,我們總能成功取得參數(shù);resource和array則不進行轉(zhuǎn)換,用戶傳入的參數(shù)必須具有指定類型,否則返回錯誤;zval作為通用結(jié)構(gòu),可以用于任何參數(shù)類型,Zend只需要簡單的將其寫入本地的接收變量。
除了類型格式符外,該函數(shù)還支持另外3個控制符:
格式字符
意義
|
后面的參數(shù)是可選的,如果用戶沒有傳遞相應(yīng)的參數(shù),則本地接收變量保持不變,這用于支持默認參數(shù);
!
前面的那個參數(shù)可以是NULL,僅用于razoOC,如果用戶傳遞的是NULL,則本地的接收zval*被設(shè)為NULL;
/
如果前面那個參數(shù)不是引用傳遞的,則不直接使用傳入的zval,而是執(zhí)行Copy-On-Write。這一點將在后面解釋。
最后,關(guān)于參數(shù)的數(shù)目也是有要求的。如果沒有采用默認參數(shù),即’|’格式符,則ZEND_NUM_ARGS()、num_args和格式串指出的參數(shù)數(shù)目這三者間必須完全匹配,否則zend_parse_parameters返回錯誤;如果使用了默認參數(shù),則ZEND_NUM_ARGS()應(yīng)和num_args相等,并且應(yīng)該落在格式串指出的參數(shù)數(shù)目區(qū)間內(nèi)。
1.2.2 底層方法
大部分情況下,使用標準方法就可以了,但有些函數(shù)可能需要處理變參,標準方法對此無能為力(*)。此時,只有使用更加原始的方法——直接獲取zval。Zend提供了如下的API:
int?zend_get_parameters_array_ex(
int?param_count,
zval?***argument_array
TSRMLS_DC);
param_count是希望獲取的參數(shù)數(shù)目,這個值不得大于ZEND_NUM_ARGS(),否則函數(shù)出錯。argument_array是一個zval**類型的數(shù)組,用于接收參數(shù)。
這個函數(shù)只是簡單的返回zval,為了使用它們,我們需要自己訪問其成員。首先是獲取參數(shù)類型,這可以通過zval.type值來判斷,可用的type見1.1.1節(jié)。之后是獲取該type對應(yīng)的值,我們可以直接訪問zval的成員,比如zval.value.lval就是long值,但更方便的方法是使用Zend提供的宏:
宏
展開
Z_LVAL(zval)
(zval).value.lval
Z_DVAL(zval)
(zval).value.dval
Z_STRVAL(zval)
(zval).value.str.val
Z_STRLEN(zval)
(zval).value.str.len
Z_ARRVAL(zval)
(zval).value.ht
Z_RESVAL(zval)
(zval).value.lval
Z_OBJVAL(zval)
(zval).value.obj
Z_BVAL (zval)
((zend_bool)(zval).value.lval)
Z_TYPE(zval)
(zval).type
一個比較特殊的宏是Z_BVAL,它不是簡單的返回值,而是進行了類型轉(zhuǎn)換。另外,這些宏都有相應(yīng)的xxx_P和xxx_PP版本,用于訪問zval*和zval**。
有時,用戶傳入?yún)?shù)的類型并不是我們期望的,這就需要手動進行類型轉(zhuǎn)換了。為此,Zend提供了如下幾個函數(shù):
convert_to_boolean_ex()
convert_to_long_ex()
convert_to_double_ex()
convert_to_string_ex()
convert_to_array_ex()
convert_to_object_ex()
convert_to_null_ex()
這些函數(shù)可將目標zval轉(zhuǎn)換成指定類型,它接收zval**作為參數(shù),為什么不用zval*呢?這是因為,這些函數(shù)有一個額外的步驟,它如果發(fā)現(xiàn)傳入的zval不是引用類型的,并且需要執(zhí)行類型轉(zhuǎn)換,則會首先執(zhí)行Copy-On-Write,并對副本施行轉(zhuǎn)換,因此,為了返回副本必須使用zval**作為參數(shù)。如果zval是引用型的,則轉(zhuǎn)換直接作用于目標zval結(jié)構(gòu)。
如果無法轉(zhuǎn)換,這些函數(shù)就會將zval設(shè)置為目標類型的虛值,比如0、FALSE、空串等,因此函數(shù)總會成功返回。
這些函數(shù)的非ex版本不執(zhí)行zval分離,而是直接作用于原zval,因此參數(shù)類型是zval*。
1.2.2? 引用傳遞
函數(shù)參數(shù)的傳遞也是采用的引用計數(shù)方式,函數(shù)棧中存放的只是zval**,它很可能和幾個變量共享一個zval。
顯然,對于引用型的zval,我們可以直接進行寫入操作;而對于非引用型的zval,并且其refcount大于1時,如果要進行寫入操作,就必須執(zhí)行zval分離(參見1.1.3)。refcount等于1的情況是因為Zend引擎已經(jīng)執(zhí)行了zval狀態(tài)切換(參見1.1.4情況II),我們得到的是自己獨占的zval,可以直接寫入。
關(guān)于傳入的zval是否引用,可以通過zval.is_ref來判斷,或者使用宏P(guān)ZVAL_IS_REF(zval*)。對于zval分離,可以使用宏SEPARATE_ZVAL(zval**),它會自動判斷refcount,并且將新zval的地址填充到參數(shù)里。
1.2.4? 編譯檢查(TODO)
上面幾節(jié)介紹了如何在我們的函數(shù)中對參數(shù)進行檢查,也就是運行時檢查,這為函數(shù)的編寫帶來了一些負擔,代碼也不夠簡潔。為此,Zend提供了編譯時檢查機制,允許我們指定函數(shù)原型,如果用戶不按規(guī)定調(diào)用,則會報錯并且跳過該函數(shù),因此,我們的函數(shù)總能得到期望的參數(shù)。
1.3返回值
從C函數(shù)向PHP返回值,并不能使用通常的return語句,導(dǎo)出函數(shù)的原型也說明了這一點:
void?zif_first_module?(
int?ht,
zval?*?return_value,
zval?**return_value_ptr,
zval?*?this_ptr,
int?return_value_used
);
因此,Zend將返回值地址作為參數(shù)傳給我們,return_value是Zend為我們預(yù)先創(chuàng)建的一個標準zval結(jié)構(gòu),相當于一個局部變量,用戶獲得返回值時就相當于對return_value進行賦值操作,我們只需填充它即可;return_value_used表明用戶是否使用了返回值,0表明沒有使用返回值,當函數(shù)結(jié)束后return_value的refcount將被減為0,并被銷毀,因此,這種情況下完全可以不處理返回值;return_value_ptr用于返回引用,它需要和zend_function_entry.arg_info聯(lián)合使用,通常都是NULL。
Zend提供了一組宏用于填充return_value:
Macro
Description
RETURN_RESOURCE(resource)
resource
RETURN_BOOL(bool)
boolean
RETURN_FALSE
false
RETURN_TRUE
true
RETURN_NULL()
NULL
RETURN_LONG(long)
long
RETURN_DOUBLE(double)
double
RETURN_STRING(string, duplicate)
字符串。string必須是C串,因為Zend將調(diào)用strlen();duplicate表示是否將傳入的C串復(fù)制一份再賦給zval,如果傳入的C串不是用Zend例程分配的,應(yīng)該指定該值
RETURN_STRINGL(string, length, duplicate)
指定字符串長度,而不是使用strlen()
RETURN_EMPTY_STRING()
空字符串
這些宏將在填充完return_value后,執(zhí)行return語句。如果不想return,可以改用相應(yīng)RETURN_xxx宏的RETVAL_xxx版本。
1.3.1? 返回引用
默認情況下,return_value_ptr是NULL,而當指定返回引用后(參見2.2.4),zend將采用*return_value_ptr作為返回值。初始狀態(tài)下,return_value 依然指向一個臨時zval,同時 *return_value_ptr = return_value。
通常應(yīng)該把return_value銷毀,并且將*return_value_ptr設(shè)為將要返回的zval*,注意要加加引用計數(shù),因為這相當于將該zval賦值給一個用作返回值的臨時變量,函數(shù)返回后,Zend會減減引用計數(shù)。
示例程序:
ZEND_FUNCTION(str_reverse)
{
if(ZEND_NUM_ARGS()!=?1)
WRONG_PARAM_COUNT;
zval?**args;
if(zend_get_parameters_array_ex(ZEND_NUM_ARGS(),?&args?TSRMLS_CC)
==?FAILURE)
{
return;
}
convert_to_string(*args);
char?swap;
char?*head=Z_STRVAL_PP(args);
char?*end=head+?Z_STRLEN_PP(args)?-?1;
for(;?headrefcount;
}
1.4啟動和終止函數(shù)
Zend允許模塊在加載和卸載時收到通知,以進行初始化和清除工作,我們要做的就是把相應(yīng)函數(shù)傳遞給Zend,它會在合適的時機自動調(diào)用。2.1.3節(jié)里留下的五個NULL就是用于這個目的,它們都是函數(shù)指針,最后一個用于配合phpinfo()來顯示模塊信息,在此忽略,只看其他四個。
Zend提供了如下四個宏,分別用于聲明對應(yīng)的函數(shù):
宏
意義
ZEND_MODULE_STARTUP_D(module)
在加載模塊時調(diào)用
ZEND_MODULE_SHUTDOWN_D(module)
在卸載模塊時調(diào)用
ZEND_MODULE_ACTIVATE_D(module)
一個頁面開始運行時調(diào)用
ZEND_MODULE_DEACTIVATE_D(module)
一個頁面運行完畢時調(diào)用
這些宏的用法和ZEND_FUNCTION宏一樣(參見2.1.1),展開后就是聲明了特定原型的函數(shù),其參數(shù)module可以是任意的,但最好使用模塊名稱。這些函數(shù)的參數(shù)中,對我們有用的是int module_number,它是模塊號,全局唯一,后面會提到其用處。
在聲明和實現(xiàn)相應(yīng)函數(shù)時,都應(yīng)該使用這些宏。最后,需要把這些函數(shù)填寫到zend_module_entry里(參見2.1.3),可按順序使用如下的宏,這些宏生成相應(yīng)的函數(shù)名稱:
ZEND_MODULE_STARTUP_N(module)
ZEND_MODULE_SHUTDOWN_N(module)
ZEND_MODULE_ACTIVATE_N(module)
ZEND_MODULE_DEACTIVATE_N(module)
1.5調(diào)用PHP函數(shù)
有時我們需要在模塊中調(diào)用用戶指定的函數(shù),比如我們實現(xiàn)了sort這樣的函數(shù),并且允許用戶指定比較函數(shù)。這可以使用如下的Zend函數(shù):
int?call_user_function_ex(
HashTable?*function_table,
zval?**object_pp,
zval?*function_name,
zval?**retval_ptr_ptr,
zend_uint?param_count,
zval?**params[],
int?no_separation,
HashTable?*symbol_table
TSRMLS_DC)
第一個參數(shù)是HashTable,在1.2.3節(jié)提到Zend使用HashTable來存儲PHP函數(shù),function_table用于指定從哪個HashTable中獲取函數(shù)。通常應(yīng)該用CG(function_table),展開就是compiler_globals.function_table,compiler_globals是一個用來存儲編譯器數(shù)據(jù)的全局數(shù)據(jù)結(jié)構(gòu)(與其對應(yīng)的還有個EG宏,即executor_globals,它用來存儲執(zhí)行器數(shù)據(jù))。compiler_globals.function_table里面存儲了所有我們可以在PHP頁面里面調(diào)用的函數(shù),包括Zend內(nèi)建函數(shù)、PHP標準庫函數(shù)、模塊導(dǎo)出的函數(shù)以及用戶使用PHP代碼定義的函數(shù)。
object_pp是一個對象,當指定該值時,Zend會從對象的函數(shù)表中獲取函數(shù),這里不予討論,總是設(shè)為NULL。
function_name必須是string型的zval,存儲我們希望調(diào)用的函數(shù)的名稱。為什么使用zval而不是直接用char*,是因為Zend考慮到大部分情況下,我們都是從用戶那獲得參數(shù),然后再調(diào)用call_user_function_ex的,這樣就可以不作處理直接把用戶參數(shù)傳給該函數(shù)。當然,我們也可以手動創(chuàng)建一個string型zval傳給它。
retval_ptr_ptr用于獲取函數(shù)的返回值,Zend執(zhí)行完指定的函數(shù)后,它就將返回值的指針填充到這里。
param_count和params用于指定函數(shù)的參數(shù),params是個zval **這點可能讓人感到奇怪,但考慮到該函數(shù)的常見用法(見下面的示例)以及2.2.2節(jié)關(guān)于函數(shù)參數(shù)的介紹,就一點也不奇怪了。
no_separation用于指定是否在必要時執(zhí)行zval分離(參見1.1.3),這在寫入非引用zval時發(fā)生。應(yīng)該總是將其設(shè)為0,表示執(zhí)行zval分離,否則可能破壞數(shù)據(jù)。
symbol_table用于指定目標函數(shù)的active_symbol_table(參見1.2.3),通常應(yīng)該使用NULL,這樣Zend會為目標函數(shù)生成一個空的符號表。
說了這么多,該動動手了,下面的程序片段簡單實現(xiàn)了PHP API call_user_func的功能:
ZEND_FUNCTION(call)
{
intnum_args=ZEND_NUM_ARGS();
if(num_args<1)
WRONG_PARAM_COUNT;
zval?***args=?(zval***)emalloc(sizeof(zval**)*num_args);
zval?*ret_zval;
//?獲取傳入的參數(shù)
if(zend_get_parameters_array_ex(num_args,?args?TSRMLS_CC)
==?FAILURE)
{
efree(args);
return;
}
//?第一個參數(shù)作為函數(shù)名,后面的作為函數(shù)參數(shù)
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");
}
//?將函數(shù)返回值反饋給用戶
*return_value=?*ret_zval;
efree(args);
}
1.6訪問PHP變量
1.6.1 設(shè)置
1.2.3節(jié)提到Zend使用HashTable來存儲全局和局部變量符號,因此訪問PHP變量,其實就是操作HashTable。當然,我們不需要手工去做,Zend提供了一組宏完成這些工作。
PHP變量的創(chuàng)建共有三步,首先需要創(chuàng)建一個zval結(jié)構(gòu),可使用如下的宏:
MAKE_STD_ZVAL(zval*)
這個宏先調(diào)用emalloc分配一塊zval,然后將其refcount設(shè)為1、is_ref設(shè)為0。
之后就是設(shè)置zval的值,同樣,我們不需要直接操作zval的成員,Zend已經(jīng)提供了如下的宏:
Macro
Description
ZVAL_RESOURCE(zval*, resource)
resource
ZVAL_BOOL(zval*, bool)
boolean
ZVAL_FALSE(zval*)
false
ZVAL_TRUE(zval*)
true
ZVAL_NULL(zval*)
NULL
ZVAL_LONG(zval*, long)
long
ZVAL_DOUBLE(zval*, double)
double
ZVAL_STRING(zval*, string, duplicate)
string必須是C串,因為Zend將調(diào)用strlen();duplicate表示是否將傳入的C串復(fù)制一份再賦給zval,如果傳入的C串不是用Zend例程分配的,應(yīng)該指定該值
ZVAL_STRINGL(zval*, string, length, duplicate)
指定字符串長度,而不是使用strlen()
ZVAL_EMPTY_STRING(zval*)
空字符串
可能你會發(fā)現(xiàn),這個表格和2.3節(jié)里面的返回值宏表格很相似,不錯,返回值宏就是直接調(diào)用的ZVAL_xxx。
既然有了zval,下面把它添加到變量符號表里就可以了,可以使用如下的一組宏:
ZEND_SET_SYMBOL(symtable,?name,?var)
ZEND_SET_GLOBAL_VAR(name,?var)
symtable用來指定你想插入的符號表,一般使用EG(active_symbol_table),表示訪問當前調(diào)用者的活動符號表。如果想強制訪問全局符號表,可以用&EG(symbol_table),這也正是ZEND_SET_GLOBAL_VAR(name, var)所做的。這兩個宏的最終效果和執(zhí)行PHP賦值語句name = var完全一樣。
如果只是訪問全局變量,可以使用單個宏代替上述三步:
SET_VAR_STRING(name,?value)
SET_VAR_STRINGL(name,?value,?length)
SET_VAR_LONG(name,?value)
SET_VAR_DOUBLE(name,?value)
上述宏分別用于創(chuàng)建全局的string、long和double變量,它們在內(nèi)部執(zhí)行了以上三步,當然,最后調(diào)用的是ZEND_SET_GLOBAL_VAR宏。
1.6.2 獲取
如果想獲取已有的PHP變量,則只能直接訪問HashTable,Zend并沒有提供相應(yīng)的操作:
int?zend_hash_find(
HashTable?*ht,
char?*arKey,?uint?nKeyLength,
void?**pData)
這個函數(shù)從HashTable中查找元素,pData用于獲取結(jié)果值,Bucket.pData將被放到這里(如果找到的話)。函數(shù)成功則返回SUCCESS,否則返回FAILURE。
下面是個示例:
zval?**ppzval;?//?Bucket.pData里存放的是zval**
if(zend_hash_find(EG(active_symbol_table),"var",?4,
(void**)&ppzval)?==?SUCCESS)
printf("var.refcount=?%d\n",?(*p)->refcount);
else
printf("Not?Found\n");
這段代碼從活動符號表中查找名為var的變量,需要注意的是nKeyLength是4,必須包括結(jié)尾的0。
獲得變量后,拿來讀是沒有問題的,但是寫操作就應(yīng)該小心對待了。只有當refcount為1或者is_ref為1,才可以寫入;否則應(yīng)該進行zval分離,具體參見1.2.3節(jié)。
1.6.3 常量
PHP常量的內(nèi)部定義如下:
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是模塊號,在啟動函數(shù)中可以獲取該值(參見2.4),當模塊被卸載時,Zend會使用模塊號查找和刪除所有該模塊注冊的常量。如果希望在模塊被卸載后,常量依然有效,可以將module_number設(shè)為0。另一個注意點是,name_len需要包含結(jié)尾的0。
flags值可以是如下兩個,可以使用”|”聯(lián)用:
flag
意義
CONST_CS
常量名大小寫敏感
CONST_PERSISTENT
持久常量,在創(chuàng)建常量的頁面執(zhí)行結(jié)束后,常量依然有效(*)
所有常量都被放在EG(zend_constants)這張HashTable里,其key是常量名稱,value是zend_constant,注意不是zend_constant*,因此HashTable會復(fù)制一份zend_constant作為value。
獲取一個常量非常簡單,只要傳遞常量名和接受常量值的zval:
int?zend_get_constant(char?*name,?uint?name_len,?zval?*result
TSRMLS_DC);
設(shè)置常量稍微復(fù)雜一點,需要先填寫一個zend_constant結(jié)構(gòu),要注意的是,常量只能是long、double和string。然后使用如下函數(shù)將其加入常量表:
同時,Zend也為我們提供了如下的宏,可以直接創(chuàng)建常量:
int?zend_register_constant(zend_constant?*c?TSRMLS_DC);
REGISTER_LONG_CONSTANT(name, value, flags)REGISTER_MAIN_LONG_CONSTANT(name, value, flags)
REGISTER_DOUBLE_CONSTANT(name, value, flags)REGISTER_MAIN_DOUBLE_CONSTANT(name, value, flags)
REGISTER_STRING_CONSTANT(name, value, flags)REGISTER_MAIN_STRING_CONSTANT(name, value, flags)
REGISTER_STRINGL_CONSTANT(name, value, length, flags)REGISTER_MAIN_STRINGL_CONSTANT(name, value, length, flags)
上述宏的MAIN版本用于創(chuàng)建module_number為0的宏,在模塊被卸載后,常量依然有效。而非MAIN版本則假設(shè)存在一個名為module_number的int變量,并拿來給zend_constant.module_number賦值,可見這組宏原本就是為在模塊啟動函數(shù)里調(diào)用而設(shè)計的。另外,當創(chuàng)建string型常量時,Zend也會dup一份字符串,因此可以直接使用C串指定常量值。
最后需要指出的是,上述函數(shù)和宏都無法改變已有的常量,如果發(fā)現(xiàn)已經(jīng)存在同名常量,則函數(shù)失敗。如果想修改的話,只能通過HashTable操作。
1.7輸出信息
Zend提供了兩個函數(shù)用于向瀏覽器輸出信息:
int?zend_printf(const?char?*format,?...);
void?zend_error(int?type,?const?char?*format,?...);
zend_printf用法和C的printf一樣;zend_error用于輸出錯誤信息,type可以指定錯誤的性質(zhì),對于不同的錯誤,Zend將作不同處理:
錯誤碼
處理
E_ERROR
嚴重錯誤,立即終止腳本運行。
E_WARNING
警告, 腳本繼續(xù)執(zhí)行。
E_PARSE
解析錯誤,解析器復(fù)位,腳本繼續(xù)執(zhí)行。
E_NOTICE
通知,腳本繼續(xù)執(zhí)行。該信息默認情況下不予輸出,可以修改php.ini來啟用。
該函數(shù)會同時輸出出錯的文件和行號,類似這樣:
Fatal?error:?no?memory?in?/home/wiki/zdj/ext/test.php?on?line?6
by zhangdongjin
總結(jié)
以上是生活随笔為你收集整理的php内核介绍及扩展开发指南 pdf vp进,PHP内核介绍及扩展开发指南—Extensions 的编写...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java string 加密_java字
- 下一篇: mysql一列数据转为一行_MySQL高