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);
}
這段代碼實現了一個簡單的extension,首先它包含了“php.h”,這是所有extensions都需要包含的頭文件,它定義、聲明了我們可以訪問的所有Zend數據結構、常量和API等。下面對剩余的步驟進行解釋。
1.1.1? 聲明導出函數
ZEND_FUNCTION(first_module);
ZEND_FUNCTION宏用于聲明一個可在PHP代碼中調用的函數,其參數即成為PHP函數名,因此,這一句聲明了一個名為first_module的PHP函數,將其展開如下:
可見,ZEND_FUNCTION就是簡單的聲明了一個名為zif_ first_module的C函數,zif可能是”Zend Internal Function”的縮寫。函數的原型滿足Zend引擎對PHP函數的調用約定,關于其參數將在后面章節進行解釋。
1.1.2? 聲明導出函數塊
聲明C函數后,Zend并不知道如何調用,我們需要使用如下的語句來完成C函數到PHP函數的映射:
zend_function_entry?firstmod_functions[]?=
{
ZEND_FE(first_module,?NULL)
{NULL,?NULL,?NULL}
};
這創建了一個zend_function_entry數組,zend_function_entry存儲了關于如何調用該PHP函數的信息,通過它Zend引擎就能夠理解和調用我們的函數。
其定義如下:
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函數名,是PHP代碼能夠通過它來調用我們的函數;handler是指向我們在前面聲明的C函數的函數指針。這兩個參數已經足以完成從C函數到PHP函數的映射。剩余的參數用于告訴Zend該PHP函數對于函數參數的要求,arg_info是個數組,它的每一項都描述了對應下標的參數,num_args是參數的個數,具體將在后面的章節介紹。
我們可以手動填充一個zend_function_entry,但更好的辦法是使用Zend提供的宏ZEND_FE,因為Zend并不保證這個結構以后不會變。ZEND_FE使用第一個參數作為PHP函數名,并且在添加了zif前綴后作為C函數名;第二個參數用于填充arg_info,通常使用NULL。上面的代碼將得到這樣一個zend_function_entry結構:{” first_module,”, zif_first_module, NULL, 0, 0}。當然,這并不是說PHP函數名必須和C函數名有什么關系,也可以通過宏ZEND_NAMED_FE來手動指定PHP函數名,不過這并不是個好主意。
我們必須為希望導出的每一個C函數都創建一個zend_function_entry結構,并將其放到一個數組中以備后用,數組最后一項的成員必須全部為NULL,這用于標記數組的結束。
1.1.3? 填寫模塊信息
下一步需要將我們的模塊介紹給Zend,主要包括我們的模塊名和導出的函數,這通過填充一個zend_module_entry結構來完成。
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宏填充了該結構的首尾部分,具體填充了什么并不是我們需要關心的,并且為了兼容后續版本也最好不要手工修改。
第二、三項是模塊名稱和導出函數,名稱可以任意填寫,導出函數就是我們在前面準備好的zend_function_entry數組。
接下來的五個參數是函數指針,其用法在后面介紹,這里只用NULL填充。
下面的參數是一個C字符串,用于表示模塊版本,如果沒有則使用NO_VERSION_YET,其實就是NULL。
填寫完畢后,需要把這個結構傳給Zend引擎,這通過下面的語句完成:
#if?COMPILE_DL_FIRST_MODULE
ZEND_GET_MODULE(firstmod)
#endif
宏開關用于判斷是否是動態鏈接的,動態鏈接時才會執行下面的語句,本文僅介紹動態鏈接的模塊,并不關心靜態鏈接時如何與Zend交流信息,因此,可以認為條件總為真。
ZEND_GET_MODULE(firstmod)最后展開得到名為get_module的一個函數:
zend_module_entry?*get_module(void)
{
return?&firstmod_module_entry;
}
這個函數就是簡單的返回我們填充的zend_module_entry結構,這里需要注意的是結構的名稱必須是xxx_module_entry,xxx是傳遞給ZEND_GET_MODULE的參數。當Zend加載我們的模塊時,它首先會解析并調用名為get_module的函數,這樣就可以得到我們的zend_module_entry,于是,PHP代碼就可以調用模塊導出的函數了。
1.1.4 實現導出函數
代碼最后一部分實現了我們導出的函數:
ZEND_FUNCTION(first_module)
{
long?parameter;
if?(zend_parse_parameters(ZEND_NUM_ARGS()?TSRMLS_CC,?"l",
?meter)?==?FAILURE)
return;
RETURN_LONG(parameter);
}
這里依然要用ZEND_FUNCTION來聲明函數原型,函數體通過Zend API和宏,訪問了函數參數并返回一個long值——這些都將在后面的章節進行詳細介紹。
1.2使用參數
函數的一個重要部分就是訪問參數,但由于extension的特殊性,我們無法像通常的函數那樣來訪問參數。
先來看導出C函數的原型:
void?zif_first_module?(
int?ht,
zval?*?return_value,
zval?**return_value_ptr,
zval?*?this_ptr,
int?return_value_used
);
ht是用戶傳入參數的數目,但一般不應直接讀取,而是通過宏ZEND_NUM_ARGS()來獲取,這通常用于判斷用戶是否傳入了規定數目的參數。下面介紹如何在我們的C函數中訪問這些參數。
1.2.1? 標準方法
常用的方法是使用下面這個函數,其使用方法類似于scanf,采用格式化字符串和變長參數列表的方式:
int?zend_parse_parameters(int?num_args?TSRMLS_DC,?char?*type_spec,?...);
num_args指出我希望獲取的參數數目,通常使用ZEND_NUM_ARGS(),因為我們一般會先用ZEND_NUM_ARGS()判斷用戶是否傳入了規定數目的參數。TSRMLS_DC宏用于線程安全,define和declare時必須這樣填寫,在調用時應該改用TSRMLS_CC。
type_spec是格式化字符串,其每個字符代表期望的當前參數的類型,之后應傳遞相應類型變量的指針來接收值,就像scanf那樣,可用的字符如下:
格式字符
PHP參數類型
接收變量類型
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是個特例,它需要兩個參數,分別獲取字符串指針和長度,這是因為PHP沒有采用C串,不能根據0來判斷字符串結尾。下面是個示例程序:
//?獲取一個long、一個string和一個resource
long?l;
char?*s;????????//?字符串地址
int?s_len;??????//?字符串長度
zval?*res;
//?檢查參數數目
if(ZEND_NUM_ARGS()?!=?3)
WRONG_PARAM_COUNT;?//?該宏輸出相應錯誤信息并退出當前函數
if?(zend_parse_parameters(ZEND_NUM_ARGS()?TSRMLS_CC,
"lsr",?&l,?&s,?&s_len,?&res)?==?FAILURE)
return;
由于PHP語法不能規定函數原型,因此用戶可以傳遞任意類型的參數,對此,zend_parse_parameters自動進行了類型檢查和轉換:在內置標量類型,即long、double、boolean和string之間,Zend會自動進行類型轉換,我們總能成功取得參數;resource和array則不進行轉換,用戶傳入的參數必須具有指定類型,否則返回錯誤;zval作為通用結構,可以用于任何參數類型,Zend只需要簡單的將其寫入本地的接收變量。
除了類型格式符外,該函數還支持另外3個控制符:
格式字符
意義
|
后面的參數是可選的,如果用戶沒有傳遞相應的參數,則本地接收變量保持不變,這用于支持默認參數;
!
前面的那個參數可以是NULL,僅用于razoOC,如果用戶傳遞的是NULL,則本地的接收zval*被設為NULL;
/
如果前面那個參數不是引用傳遞的,則不直接使用傳入的zval,而是執行Copy-On-Write。這一點將在后面解釋。
最后,關于參數的數目也是有要求的。如果沒有采用默認參數,即’|’格式符,則ZEND_NUM_ARGS()、num_args和格式串指出的參數數目這三者間必須完全匹配,否則zend_parse_parameters返回錯誤;如果使用了默認參數,則ZEND_NUM_ARGS()應和num_args相等,并且應該落在格式串指出的參數數目區間內。
1.2.2 底層方法
大部分情況下,使用標準方法就可以了,但有些函數可能需要處理變參,標準方法對此無能為力(*)。此時,只有使用更加原始的方法——直接獲取zval。Zend提供了如下的API:
int?zend_get_parameters_array_ex(
int?param_count,
zval?***argument_array
TSRMLS_DC);
param_count是希望獲取的參數數目,這個值不得大于ZEND_NUM_ARGS(),否則函數出錯。argument_array是一個zval**類型的數組,用于接收參數。
這個函數只是簡單的返回zval,為了使用它們,我們需要自己訪問其成員。首先是獲取參數類型,這可以通過zval.type值來判斷,可用的type見1.1.1節。之后是獲取該type對應的值,我們可以直接訪問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,它不是簡單的返回值,而是進行了類型轉換。另外,這些宏都有相應的xxx_P和xxx_PP版本,用于訪問zval*和zval**。
有時,用戶傳入參數的類型并不是我們期望的,這就需要手動進行類型轉換了。為此,Zend提供了如下幾個函數:
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()
這些函數可將目標zval轉換成指定類型,它接收zval**作為參數,為什么不用zval*呢?這是因為,這些函數有一個額外的步驟,它如果發現傳入的zval不是引用類型的,并且需要執行類型轉換,則會首先執行Copy-On-Write,并對副本施行轉換,因此,為了返回副本必須使用zval**作為參數。如果zval是引用型的,則轉換直接作用于目標zval結構。
如果無法轉換,這些函數就會將zval設置為目標類型的虛值,比如0、FALSE、空串等,因此函數總會成功返回。
這些函數的非ex版本不執行zval分離,而是直接作用于原zval,因此參數類型是zval*。
1.2.2? 引用傳遞
函數參數的傳遞也是采用的引用計數方式,函數棧中存放的只是zval**,它很可能和幾個變量共享一個zval。
顯然,對于引用型的zval,我們可以直接進行寫入操作;而對于非引用型的zval,并且其refcount大于1時,如果要進行寫入操作,就必須執行zval分離(參見1.1.3)。refcount等于1的情況是因為Zend引擎已經執行了zval狀態切換(參見1.1.4情況II),我們得到的是自己獨占的zval,可以直接寫入。
關于傳入的zval是否引用,可以通過zval.is_ref來判斷,或者使用宏PZVAL_IS_REF(zval*)。對于zval分離,可以使用宏SEPARATE_ZVAL(zval**),它會自動判斷refcount,并且將新zval的地址填充到參數里。
1.2.4? 編譯檢查(TODO)
上面幾節介紹了如何在我們的函數中對參數進行檢查,也就是運行時檢查,這為函數的編寫帶來了一些負擔,代碼也不夠簡潔。為此,Zend提供了編譯時檢查機制,允許我們指定函數原型,如果用戶不按規定調用,則會報錯并且跳過該函數,因此,我們的函數總能得到期望的參數。
1.3返回值
從C函數向PHP返回值,并不能使用通常的return語句,導出函數的原型也說明了這一點:
void?zif_first_module?(
int?ht,
zval?*?return_value,
zval?**return_value_ptr,
zval?*?this_ptr,
int?return_value_used
);
因此,Zend將返回值地址作為參數傳給我們,return_value是Zend為我們預先創建的一個標準zval結構,相當于一個局部變量,用戶獲得返回值時就相當于對return_value進行賦值操作,我們只需填充它即可;return_value_used表明用戶是否使用了返回值,0表明沒有使用返回值,當函數結束后return_value的refcount將被減為0,并被銷毀,因此,這種情況下完全可以不處理返回值;return_value_ptr用于返回引用,它需要和zend_function_entry.arg_info聯合使用,通常都是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將調用strlen();duplicate表示是否將傳入的C串復制一份再賦給zval,如果傳入的C串不是用Zend例程分配的,應該指定該值
RETURN_STRINGL(string, length, duplicate)
指定字符串長度,而不是使用strlen()
RETURN_EMPTY_STRING()
空字符串
這些宏將在填充完return_value后,執行return語句。如果不想return,可以改用相應RETURN_xxx宏的RETVAL_xxx版本。
1.3.1? 返回引用
默認情況下,return_value_ptr是NULL,而當指定返回引用后(參見2.2.4),zend將采用*return_value_ptr作為返回值。初始狀態下,return_value 依然指向一個臨時zval,同時 *return_value_ptr = return_value。
通常應該把return_value銷毀,并且將*return_value_ptr設為將要返回的zval*,注意要加加引用計數,因為這相當于將該zval賦值給一個用作返回值的臨時變量,函數返回后,Zend會減減引用計數。
示例程序:
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啟動和終止函數
Zend允許模塊在加載和卸載時收到通知,以進行初始化和清除工作,我們要做的就是把相應函數傳遞給Zend,它會在合適的時機自動調用。2.1.3節里留下的五個NULL就是用于這個目的,它們都是函數指針,最后一個用于配合phpinfo()來顯示模塊信息,在此忽略,只看其他四個。
Zend提供了如下四個宏,分別用于聲明對應的函數:
宏
意義
ZEND_MODULE_STARTUP_D(module)
在加載模塊時調用
ZEND_MODULE_SHUTDOWN_D(module)
在卸載模塊時調用
ZEND_MODULE_ACTIVATE_D(module)
一個頁面開始運行時調用
ZEND_MODULE_DEACTIVATE_D(module)
一個頁面運行完畢時調用
這些宏的用法和ZEND_FUNCTION宏一樣(參見2.1.1),展開后就是聲明了特定原型的函數,其參數module可以是任意的,但最好使用模塊名稱。這些函數的參數中,對我們有用的是int module_number,它是模塊號,全局唯一,后面會提到其用處。
在聲明和實現相應函數時,都應該使用這些宏。最后,需要把這些函數填寫到zend_module_entry里(參見2.1.3),可按順序使用如下的宏,這些宏生成相應的函數名稱:
ZEND_MODULE_STARTUP_N(module)
ZEND_MODULE_SHUTDOWN_N(module)
ZEND_MODULE_ACTIVATE_N(module)
ZEND_MODULE_DEACTIVATE_N(module)
1.5調用PHP函數
有時我們需要在模塊中調用用戶指定的函數,比如我們實現了sort這樣的函數,并且允許用戶指定比較函數。這可以使用如下的Zend函數:
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)
第一個參數是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的功能:
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;
//?獲取傳入的參數
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結構,可使用如下的宏:
MAKE_STD_ZVAL(zval*)
這個宏先調用emalloc分配一塊zval,然后將其refcount設為1、is_ref設為0。
之后就是設置zval的值,同樣,我們不需要直接操作zval的成員,Zend已經提供了如下的宏:
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將調用strlen();duplicate表示是否將傳入的C串復制一份再賦給zval,如果傳入的C串不是用Zend例程分配的,應該指定該值
ZVAL_STRINGL(zval*, string, length, duplicate)
指定字符串長度,而不是使用strlen()
ZVAL_EMPTY_STRING(zval*)
空字符串
可能你會發現,這個表格和2.3節里面的返回值宏表格很相似,不錯,返回值宏就是直接調用的ZVAL_xxx。
既然有了zval,下面把它添加到變量符號表里就可以了,可以使用如下的一組宏:
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完全一樣。
如果只是訪問全局變量,可以使用單個宏代替上述三步:
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宏。
1.6.2 獲取
如果想獲取已有的PHP變量,則只能直接訪問HashTable,Zend并沒有提供相應的操作:
int?zend_hash_find(
HashTable?*ht,
char?*arKey,?uint?nKeyLength,
void?**pData)
這個函數從HashTable中查找元素,pData用于獲取結果值,Bucket.pData將被放到這里(如果找到的話)。函數成功則返回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,必須包括結尾的0。
獲得變量后,拿來讀是沒有問題的,但是寫操作就應該小心對待了。只有當refcount為1或者is_ref為1,才可以寫入;否則應該進行zval分離,具體參見1.2.3節。
1.6.3 常量
PHP常量的內部定義如下:
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值可以是如下兩個,可以使用”|”聯用:
flag
意義
CONST_CS
常量名大小寫敏感
CONST_PERSISTENT
持久常量,在創建常量的頁面執行結束后,常量依然有效(*)
所有常量都被放在EG(zend_constants)這張HashTable里,其key是常量名稱,value是zend_constant,注意不是zend_constant*,因此HashTable會復制一份zend_constant作為value。
獲取一個常量非常簡單,只要傳遞常量名和接受常量值的zval:
int?zend_get_constant(char?*name,?uint?name_len,?zval?*result
TSRMLS_DC);
設置常量稍微復雜一點,需要先填寫一個zend_constant結構,要注意的是,常量只能是long、double和string。然后使用如下函數將其加入常量表:
同時,Zend也為我們提供了如下的宏,可以直接創建常量:
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版本用于創建module_number為0的宏,在模塊被卸載后,常量依然有效。而非MAIN版本則假設存在一個名為module_number的int變量,并拿來給zend_constant.module_number賦值,可見這組宏原本就是為在模塊啟動函數里調用而設計的。另外,當創建string型常量時,Zend也會dup一份字符串,因此可以直接使用C串指定常量值。
最后需要指出的是,上述函數和宏都無法改變已有的常量,如果發現已經存在同名常量,則函數失敗。如果想修改的話,只能通過HashTable操作。
1.7輸出信息
Zend提供了兩個函數用于向瀏覽器輸出信息:
int?zend_printf(const?char?*format,?...);
void?zend_error(int?type,?const?char?*format,?...);
zend_printf用法和C的printf一樣;zend_error用于輸出錯誤信息,type可以指定錯誤的性質,對于不同的錯誤,Zend將作不同處理:
錯誤碼
處理
E_ERROR
嚴重錯誤,立即終止腳本運行。
E_WARNING
警告, 腳本繼續執行。
E_PARSE
解析錯誤,解析器復位,腳本繼續執行。
E_NOTICE
通知,腳本繼續執行。該信息默認情況下不予輸出,可以修改php.ini來啟用。
該函數會同時輸出出錯的文件和行號,類似這樣:
Fatal?error:?no?memory?in?/home/wiki/zdj/ext/test.php?on?line?6
by zhangdongjin
總結
以上是生活随笔為你收集整理的php内核介绍及扩展开发指南 pdf vp进,PHP内核介绍及扩展开发指南—Extensions 的编写...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java string 加密_java字
- 下一篇: mysql一列数据转为一行_MySQL高