溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么用C/C++擴(kuò)展你的PHP 為你的php增加功能

發(fā)布時(shí)間:2021-03-08 14:55:21 來(lái)源:億速云 閱讀:344 作者:TREX 欄目:開(kāi)發(fā)技術(shù)

這篇文章主要介紹“怎么用C/C++擴(kuò)展你的PHP 為你的php增加功能”,在日常操作中,相信很多人在怎么用C/C++擴(kuò)展你的PHP 為你的php增加功能問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”怎么用C/C++擴(kuò)展你的PHP 為你的php增加功能”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!



PHP取得成功的一個(gè)主要原因之一是她擁有大量的可用擴(kuò)展。web開(kāi)發(fā)者無(wú)論有何種需求,這種需求最有可能在PHP發(fā)行包里找到。PHP發(fā)行包包括支持各種數(shù)據(jù)庫(kù),圖形文件格式,壓縮,XML技術(shù)擴(kuò)展在內(nèi)的許多擴(kuò)展。
擴(kuò)展API的引入使PHP3取得了巨大的進(jìn)展,擴(kuò)展API機(jī)制使PHP開(kāi)發(fā)社區(qū)很容易的開(kāi)發(fā)出幾十種擴(kuò)展?,F(xiàn)在,兩個(gè)版本過(guò)去了,API仍然和PHP3時(shí)的非常相似。擴(kuò)展主要的思想是:盡可能的從擴(kuò)展編寫(xiě)者那里隱藏PHP的內(nèi)部機(jī)制和腳本引擎本身,僅僅需要開(kāi)發(fā)者熟悉API。

有兩個(gè)理由需要自己編寫(xiě)PHP擴(kuò)展。第一個(gè)理由是:PHP需要支持一項(xiàng)她還未支持的技術(shù)。這通常包括包裹一些現(xiàn)成的C函數(shù)庫(kù),以便提供PHP接口。例如,如果一個(gè)叫FooBase的數(shù)據(jù)庫(kù)已推出市場(chǎng),你需要建立一個(gè)PHP擴(kuò)展幫助你從PHP里調(diào)用FooBase的C函數(shù)庫(kù)。這個(gè)工作可能僅由一個(gè)人完成,然后被整個(gè)PHP社區(qū)共享(如果你愿意的話)。第二個(gè)不是很普遍的理由是:你需要從性能或功能的原因考慮來(lái)編寫(xiě)一些商業(yè)邏輯。

如果以上的兩個(gè)理由都和你沒(méi)什么關(guān)系,同時(shí)你感覺(jué)自己沒(méi)有冒險(xiǎn)精神,那么你可以跳過(guò)本章。

本章教你如何編寫(xiě)相對(duì)簡(jiǎn)單的PHP擴(kuò)展,使用一部分?jǐn)U展API函數(shù)。對(duì)于大多數(shù)打算開(kāi)發(fā)自定義PHP擴(kuò)展開(kāi)發(fā)者而言,它含概了足夠的資料。學(xué)習(xí)一門(mén)編程課程的最好方法之一就是動(dòng)手做一些極其簡(jiǎn)單的例子,這些例子正是本章的線索。一旦你明白了基礎(chǔ)的東西,你就可以在互聯(lián)網(wǎng)上通過(guò)閱讀文擋、原代碼或參加郵件列表新聞組討論來(lái)豐富自己。因此,本章集中在讓你如何開(kāi)始的話題。在UNIX下一個(gè)叫ext_skel的腳本被用于建立擴(kuò)展的骨架,骨架信息從一個(gè)描述擴(kuò)展接口的定義文件中取得。因此你需要利用UNIX來(lái)建立一個(gè)骨架。Windows開(kāi)發(fā)者可以使用Windows ext_skel_win32.php代替ext_skel。

然而,本章關(guān)于用你開(kāi)發(fā)的擴(kuò)展編譯PHP的指導(dǎo)僅涉及UNIX編譯系統(tǒng)。本章中所有的對(duì)API的解釋與UNIX和Windows下開(kāi)發(fā)的擴(kuò)展都有聯(lián)系。

當(dāng)你閱讀完這章,你能學(xué)會(huì)如何

?建立一個(gè)簡(jiǎn)單的商業(yè)邏輯擴(kuò)展。
?建議個(gè)C函數(shù)庫(kù)的包裹擴(kuò)展,尤其是有些標(biāo)準(zhǔn)C文件操作函數(shù)比如fopen()
快速開(kāi)始
本節(jié)沒(méi)有介紹關(guān)于腳本引擎基本構(gòu)造的一些知識(shí),而是直接進(jìn)入擴(kuò)展的編碼講解中,因此不要擔(dān)心你無(wú)法立刻獲得對(duì)擴(kuò)展整體把握的感覺(jué)。假設(shè)你正在開(kāi)發(fā)一個(gè)網(wǎng)站,需要一個(gè)把字符串重復(fù)n次的函數(shù)。下面是用PHP寫(xiě)的例子:

復(fù)制代碼 代碼如下:


function self_concat($string, $n){
$result = "";
for($i = 0; $i < $n; $i++){
$result .= $string;
}
return $result;
}
self_concat("One", 3) returns "OneOneOne".
self_concat("One", 1) returns "One".



假設(shè)由于一些奇怪的原因,你需要時(shí)常調(diào)用這個(gè)函數(shù),而且還要傳給函數(shù)很長(zhǎng)的字符串和大值n。這意味著在腳本里有相當(dāng)巨大的字符串連接量和內(nèi)存重新分配過(guò)程,以至顯著地降低腳本執(zhí)行速度。如果有一個(gè)函數(shù)能夠更快地分配大量且足夠的內(nèi)存來(lái)存放結(jié)果字符串,然后把$string重復(fù)n次,就不需要在每次循環(huán)迭代中分配內(nèi)存。

為擴(kuò)展建立函數(shù)的第一步是寫(xiě)一個(gè)函數(shù)定義文件,該函數(shù)定義文件定義了擴(kuò)展對(duì)外提供的函數(shù)原形。該例中,定義函數(shù)只有一行函數(shù)原形self_concat() :

復(fù)制代碼 代碼如下:


string self_concat(string str, int n)



函數(shù)定義文件的一般格式是一個(gè)函數(shù)一行。你可以定義可選參數(shù)和使用大量的PHP類(lèi)型,包括: bool, float, int, array等。

保存為myfunctions.def文件至PHP原代碼目錄樹(shù)下。

該是通過(guò)擴(kuò)展骨架(skeleton)構(gòu)造器運(yùn)行函數(shù)定義文件的時(shí)機(jī)了。該構(gòu)造器腳本叫ext_skel,放在PHP原代碼目錄樹(shù)的ext/目錄下(PHP原碼主目錄下的README.EXT_SKEL提供了更多的信息)。假設(shè)你把函數(shù)定義保存在一個(gè)叫做myfunctions.def的文件里,而且你希望把擴(kuò)展取名為myfunctions,運(yùn)行下面的命令來(lái)建立擴(kuò)展骨架

復(fù)制代碼 代碼如下:


./ext_skel --extname=myfunctions --proto=myfunctions.de



這個(gè)命令在ext/目錄下建立了一個(gè)myfunctions/目錄。你要做的第一件事情也許就是編譯該骨架,以便編寫(xiě)和測(cè)試實(shí)際的C代碼。編譯擴(kuò)展有兩種方法:

?作為一個(gè)可裝載模塊或者DSO(動(dòng)態(tài)共享對(duì)象)
?靜態(tài)編譯到PHP
怎么用C/C++擴(kuò)展你的PHP 為你的php增加功能
PHP擴(kuò)展開(kāi)發(fā)導(dǎo)圖

因?yàn)榈诙N方法比較容易上手,所以本章采用靜態(tài)編譯。如果你對(duì)編譯可裝載擴(kuò)展模塊感興趣,可以閱讀PHP原代碼根目錄下的README.SELF-CONTAINED_EXTENSIONS文件。為了使擴(kuò)展能夠被編譯,需要修改擴(kuò)展目錄ext/myfunctions/下的config.m4文件。擴(kuò)展沒(méi)有包裹任何外部的C庫(kù),你需要添加支持–enable-myfunctions配置開(kāi)關(guān)到PHP編譯系統(tǒng)里(–with-extension 開(kāi)關(guān)用于那些需要用戶指定相關(guān)C庫(kù)路徑的擴(kuò)展)。可以去掉自動(dòng)生成的下面兩行的注釋來(lái)開(kāi)啟這個(gè)配置。

復(fù)制代碼 代碼如下:


./ext_skel --extname=myfunctions --proto=myfunctions.def
PHP_ARG_ENABLE(myfunctions, whether to enable myfunctions support,
[ --enable-myfunctions Include myfunctions support]



現(xiàn)在剩下的事情就是在PHP原代碼樹(shù)根目錄下運(yùn)行./buildconf,該命令會(huì)生成一個(gè)新的配置腳本。通過(guò)查看./configure –help輸出信息,可以檢查新的配置選項(xiàng)是否被包含到配置文件中。現(xiàn)在,打開(kāi)你喜好的配置選項(xiàng)開(kāi)關(guān)和–enable-myfunctions重新配置一下PHP。最后的但不是最次要的是,用make來(lái)重新編譯PHP。

ext_skel應(yīng)該把兩個(gè)PHP函數(shù)添加到你的擴(kuò)展骨架了:打算實(shí)現(xiàn)的self_concat()函數(shù)和用于檢測(cè)myfunctions 是否編譯到PHP的confirm_myfunctions_compiled()函數(shù)。完成PHP的擴(kuò)展開(kāi)發(fā)后,可以把后者去掉。

復(fù)制代碼 代碼如下:


<?php
print confirm_myfunctions_compiled("myextension");
?>


運(yùn)行這個(gè)腳本會(huì)出現(xiàn)類(lèi)似下面的輸出:

復(fù)制代碼 代碼如下:


"Congratulations! You have successfully modified ext/myfunctions
config.m4. Module myfunctions is now compiled into PHP.


另外,ext_skel腳本生成一個(gè)叫myfunctions.php的腳本,你也可以利用它來(lái)驗(yàn)證擴(kuò)展是否被成功地編譯到PHP。它會(huì)列出該擴(kuò)展所支持的所有函數(shù)。
現(xiàn)在你學(xué)會(huì)如何編譯擴(kuò)展了,該是真正地研究self_concat()函數(shù)的時(shí)候了。
下面就是ext_skel腳本生成的骨架結(jié)構(gòu):

復(fù)制代碼 代碼如下:


/* {{{ proto string self_concat(string str, int n)
*/
PHP_FUNCTION(self_concat)
{
char *str = NULL;
int argc = ZEND_NUM_ARGS();
int str_len;
long n;
if (zend_parse_parameters(argc TSRMLS_CC, "sl", &str, &str_len, &n) == FAILURE)
return;
php_error(E_WARNING, "self_concat: not yet implemented");
}
/* }}} */



自動(dòng)生成的PHP函數(shù)周?chē)艘恍┳⑨專(zhuān)@些注釋用于自動(dòng)生成代碼文檔和vi、Emacs等編輯器的代碼折疊。函數(shù)自身的定義使用了宏P(guān)HP_FUNCTION(),該宏可以生成一個(gè)適合于Zend引擎的函數(shù)原型。邏輯本身分成語(yǔ)義各部分,取得調(diào)用函數(shù)的參數(shù)和邏輯本身。

為了獲得函數(shù)傳遞的參數(shù),可以使用zend_parse_parameters()API函數(shù)。下面是該函數(shù)的原型:

復(fù)制代碼 代碼如下:


zend_parse_parameters(int num_args TSRMLS_DC, char *type_spec, …);


第一個(gè)參數(shù)是傳遞給函數(shù)的參數(shù)個(gè)數(shù)。通常的做法是傳給它ZEND_NUM_ARGS()。這是一個(gè)表示傳遞給函數(shù)參數(shù)總個(gè)數(shù)的宏。第二個(gè)參數(shù)是為了線程安全,總是傳遞TSRMLS_CC宏,后面會(huì)講到。第三個(gè)參數(shù)是一個(gè)字符串,指定了函數(shù)期望的參數(shù)類(lèi)型,后面緊跟著需要隨參數(shù)值更新的變量列表。因?yàn)镻HP采用松散的變量定義和動(dòng)態(tài)的類(lèi)型判斷,這樣做就使得把不同類(lèi)型的參數(shù)轉(zhuǎn)化為期望的類(lèi)型成為可能。例如,如果用戶傳遞一個(gè)整數(shù)變量,可函數(shù)需要一個(gè)浮點(diǎn)數(shù),那么zend_parse_parameters()就會(huì)自動(dòng)地把整數(shù)轉(zhuǎn)換為相應(yīng)的浮點(diǎn)數(shù)。如果實(shí)際值無(wú)法轉(zhuǎn)換成期望類(lèi)型(比如整形到數(shù)組形),會(huì)觸發(fā)一個(gè)警告。

下表列出了可能指定的類(lèi)型。我們從完整性考慮也列出了一些沒(méi)有討論到的類(lèi)型。

類(lèi)型指定符對(duì)應(yīng)的C類(lèi)型描述
llong符號(hào)整數(shù)
ddouble浮點(diǎn)數(shù)
schar *, int二進(jìn)制字符串,長(zhǎng)度
bzend_bool邏輯型(1或0)
rzval *資源(文件指針,數(shù)據(jù)庫(kù)連接等)
azval *聯(lián)合數(shù)組
ozval *任何類(lèi)型的對(duì)象
Ozval *指定類(lèi)型的對(duì)象。需要提供目標(biāo)對(duì)象的類(lèi)類(lèi)型
zzval *無(wú)任何操作的zval


為了容易地理解最后幾個(gè)選項(xiàng)的含義,你需要知道zval是Zend引擎的值容器[1]。無(wú)論這個(gè)變量是布爾型,字符串型或者其他任何類(lèi)型,其信息總會(huì)包含在一個(gè)zval聯(lián)合體中。本章中我們不直接存取zval,而是通過(guò)一些附加的宏來(lái)操作。下面的是或多或少在C中的zval, 以便我們能更好地理解接下來(lái)的代碼。

復(fù)制代碼 代碼如下:


typedef union _zval{
long lval;
double dval;
struct {
char *val;
int len;
}str;
HashTable *ht;
zend_object_value obj;
}zval;



在我們的例子中,我們用基本類(lèi)型調(diào)用zend_parse_parameters(),以本地C類(lèi)型的方式取得函數(shù)參數(shù)的值,而不是用zval容器。

為了讓zend_parse_parameters()能夠改變傳遞給它的參數(shù)的值,并返回這個(gè)改變值,需要傳遞一個(gè)引用。仔細(xì)查看一下self_concat():

復(fù)制代碼 代碼如下:


if (zend_parse_parameters(argc TSRMLS_CC, "sl", &str, &str_len, &n) == FAILURE)return;



注意到自動(dòng)生成的代碼會(huì)檢測(cè)函數(shù)的返回值FAILUER(成功即SUCCESS)來(lái)判斷是否成功。如果沒(méi)有成功則立即返回,并且由zend_parse_parameters()負(fù)責(zé)觸發(fā)警告信息。因?yàn)楹瘮?shù)打算接收一個(gè)字符串l和一個(gè)整數(shù)n,所以指定 ”sl” 作為其類(lèi)型指示符。s需要兩個(gè)參數(shù),所以我們傳遞參考char * 和 int (str 和 str_len)給zend_parse_parameters()函數(shù)。無(wú)論什么時(shí)候,記得總是在代碼中使用字符串長(zhǎng)度str_len來(lái)確保函數(shù)工作在二進(jìn)制安全的環(huán)境中。不要使用strlen()和strcpy(),除非你不介意函數(shù)在二進(jìn)制字符串下不能工作。二進(jìn)制字符串是包含有nulls的字符串。二進(jìn)制格式包括圖象文件,壓縮文件,可執(zhí)行文件和更多的其他文件。”l” 只需要一個(gè)參數(shù),所以我們傳遞給它n的引用。盡管為了清晰起見(jiàn),骨架腳本生成的C變量名與在函數(shù)原型定義文件中的參數(shù)名一樣;這樣做不是必須的,盡管在實(shí)踐中鼓勵(lì)這樣做。

回到轉(zhuǎn)換規(guī)則中來(lái)。下面三個(gè)對(duì)self_concat()函數(shù)的調(diào)用使str, str_len和n得到同樣的值:

復(fù)制代碼 代碼如下:


self_concat("321", 5);
self_concat(321, "5");
self_concat("321", "5");
str points to the string "321", str_len equals 3, and n equals 5.
str 指向字符串"321",str_len等于3,n等于5



在我們編寫(xiě)代碼來(lái)實(shí)現(xiàn)連接字符串返回給PHP的函數(shù)前,還得談?wù)剝蓚€(gè)重要的話題:內(nèi)存管理、從PHP內(nèi)部返回函數(shù)值所使用的API。

內(nèi)存管理

用于從堆中分配內(nèi)存的PHP API幾乎和標(biāo)準(zhǔn)C API一樣。在編寫(xiě)擴(kuò)展的時(shí)候,使用下面與C對(duì)應(yīng)(因此不必再解釋?zhuān)┑腁PI函數(shù):

復(fù)制代碼 代碼如下:


emalloc(size_t size);
efree(void *ptr);
ecalloc(size_t nmemb, size_t size);
erealloc(void *ptr, size_t size);
estrdup(const char *s);
estrndup(const char *s, unsigned int length);


在這一點(diǎn)上,任何一位有經(jīng)驗(yàn)的C程序員應(yīng)該象這樣思考一下:“什么?標(biāo)準(zhǔn)C沒(méi)有strndup()?”是的,這是正確的,因?yàn)镚NU擴(kuò)展通常在Linux下可用。estrndup()只是PHP下的一個(gè)特殊函數(shù)。它的行為與estrdup()相似,但是可以指定字符串重復(fù)的次數(shù)(不需要結(jié)束空字符),同時(shí)是二進(jìn)制安全的。這是推薦使用estrndup()而不是estrdup()的原因。

在幾乎所有的情況下,你應(yīng)該使用這些內(nèi)存分配函數(shù)。有一些情況,即擴(kuò)展需要分配在請(qǐng)求中永久存在的內(nèi)存,從而不得不使用malloc(),但是除非你知道你在做什么,你應(yīng)該始終使用以上的函數(shù)。如果沒(méi)有使用這些內(nèi)存函數(shù),而相反使用標(biāo)準(zhǔn)C函數(shù)分配的內(nèi)存返回給腳本引擎,那么PHP會(huì)崩潰。

這些函數(shù)的優(yōu)點(diǎn)是:任何分配的內(nèi)存在偶然情況下如果沒(méi)有被釋放,則會(huì)在頁(yè)面請(qǐng)求的最后被釋放。因此,真正的內(nèi)存泄漏不會(huì)產(chǎn)生。然而,不要依賴這一機(jī)制,從調(diào)試和性能兩個(gè)原因來(lái)考慮,應(yīng)當(dāng)確保釋放應(yīng)該釋放的內(nèi)存。剩下的優(yōu)點(diǎn)是在多線程環(huán)境下性能的提高,調(diào)試模式下檢測(cè)內(nèi)存錯(cuò)誤等。

還有一個(gè)重要的原因,你不需要檢查這些內(nèi)存分配函數(shù)的返回值是否為null。當(dāng)內(nèi)存分配失敗,它們會(huì)發(fā)出E_ERROR錯(cuò)誤,從而決不會(huì)返回到擴(kuò)展。

從PHP函數(shù)中返回值

擴(kuò)展API包含豐富的用于從函數(shù)中返回值的宏。這些宏有兩種主要風(fēng)格:第一種是RETVAL_type()形式,它設(shè)置了返回值但C代碼繼續(xù)執(zhí)行。這通常使用在把控制交給腳本引擎前還希望做的一些清理工作的時(shí)候使用,然后再使用C的返回聲明 ”return” 返回到PHP;后一個(gè)宏更加普遍,其形式是RETURN_type(),他設(shè)置了返回類(lèi)型,同時(shí)返回控制到PHP。下表解釋了大多數(shù)存在的宏。

設(shè)置返回值并且結(jié)束函數(shù)設(shè)置返回值宏返回類(lèi)型和參數(shù)
RETURN_LONG(l)RETVAL_LONG(l)整數(shù)
RETURN_BOOL(b)RETVAL_BOOL(b)布爾數(shù)(1或0)
RETURN_NULL()RETVAL_NULL()NULL
RETURN_DOUBLE(d)RETVAL_DOUBLE(d)浮點(diǎn)數(shù)
RETURN_STRING(s, dup)RETVAL_STRING(s, dup)字符串。如果dup為1,引擎會(huì)調(diào)用estrdup()重復(fù)s,使用拷貝。如果dup為0,就使用s
RETURN_STRINGL(s, l, dup)RETVAL_STRINGL(s, l, dup)長(zhǎng)度為l的字符串值。與上一個(gè)宏一樣,但因?yàn)閟的長(zhǎng)度被指定,所以速度更快。
RETURN_TRUERETVAL_TRUE返回布爾值true。注意到這個(gè)宏沒(méi)有括號(hào)。
RETURN_FALSERETVAL_FALSE返回布爾值false。注意到這個(gè)宏沒(méi)有括號(hào)。
RETURN_RESOURCE(r)RETVAL_RESOURCE(r)資源句柄。


完成self_concat()

現(xiàn)在你已經(jīng)學(xué)會(huì)了如何分配內(nèi)存和從PHP擴(kuò)展函數(shù)里返回函數(shù)值,那么我們就能夠完成self_concat()的編碼:

復(fù)制代碼 代碼如下:


/* {{{ proto string self_concat(string str, int n)
*/
PHP_FUNCTION(self_concat)
}
char *str = NULL;
int argc = ZEND_NUM_ARGS();
int str_len;
long n;
char *result; /* Points to resulting string */
char *ptr; /* Points at the next location we want to copy to */
int result_length; /* Length of resulting string */
if (zend_parse_parameters(argc TSRMLS_CC, "sl", &str, &str_len, &n) == FAILURE)
return;
/* Calculate length of result */
result_length = (str_len * n);
/* Allocate memory for result */
result = (char *) emalloc(result_length + 1);
/* Point at the beginning of the result */
ptr = result;
while (n--) {
/* Copy str to the result */
memcpy(ptr, str, str_len);
/* Increment ptr to point at the next position we want to write to */
ptr += str_len;
}
/* Null terminate the result. Always null-terminate your strings
even if they are binary strings */
*ptr = '\0';
/* Return result to the scripting engine without duplicating it*/
RETURN_STRINGL(result, result_length, 0);
}
/* }}} */


現(xiàn)在要做的就是重新編譯一下PHP,這樣就完成了第一個(gè)PHP函數(shù)。

讓我門(mén)檢查函數(shù)是否真的工作。在最新編譯過(guò)的PHP樹(shù)下執(zhí)行[2]下面的腳本:

復(fù)制代碼 代碼如下:


<?php
for ($i = 1; $i <= 3; $i++){
print self_concat("ThisIsUseless", $i);
print "\n";
}
?>


你應(yīng)該得到下面的結(jié)果:

復(fù)制代碼 代碼如下:


ThisIsUseless
ThisIsUselessThisIsUseless
ThisIsUselessThisIsUselessThisIsUseles


實(shí)例小結(jié)
你已經(jīng)學(xué)會(huì)如何編寫(xiě)一個(gè)簡(jiǎn)單的PHP函數(shù)?;氐奖菊碌拈_(kāi)頭,我們提到用C編寫(xiě)PHP功能函數(shù)的兩個(gè)主要的動(dòng)機(jī)。第一個(gè)動(dòng)機(jī)是用C實(shí)現(xiàn)一些算法來(lái)提高性能和擴(kuò)展功能。前一個(gè)例子應(yīng)該能夠指導(dǎo)你快速上手這種類(lèi)型擴(kuò)展的開(kāi)發(fā)。第二個(gè)動(dòng)機(jī)是包裹三方函數(shù)庫(kù)。我們將在下一步討論。

包裹第三方的擴(kuò)展
本節(jié)中你將學(xué)到如何編寫(xiě)更有用和更完善的擴(kuò)展。該節(jié)的擴(kuò)展包裹了一個(gè)C庫(kù),展示了如何編寫(xiě)一個(gè)含有多個(gè)互相依賴的PHP函數(shù)擴(kuò)展。

動(dòng)機(jī)
也許最常見(jiàn)的PHP擴(kuò)展是那些包裹第三方C庫(kù)的擴(kuò)展。這些擴(kuò)展包括MySQL或Oracle的數(shù)據(jù)庫(kù)服務(wù)庫(kù),libxml2的 XML技術(shù)庫(kù),ImageMagick 或GD的圖形操縱庫(kù)。

在本節(jié)中,我們編寫(xiě)一個(gè)擴(kuò)展,同樣使用腳本來(lái)生成骨架擴(kuò)展,因?yàn)檫@能節(jié)省許多工作量。這個(gè)擴(kuò)展包裹了標(biāo)準(zhǔn)C函數(shù)fopen(), fclose(), fread(), fwrite()和 feof().

擴(kuò)展使用一個(gè)被叫做資源的抽象數(shù)據(jù)類(lèi)型,用于代表已打開(kāi)的文件FILE*。你會(huì)注意到大多數(shù)處理比如數(shù)據(jù)庫(kù)連接、文件句柄等的PHP擴(kuò)展使用了資源類(lèi)型,這是因?yàn)橐孀约簾o(wú)法直接“理解”它們。我們計(jì)劃在PHP擴(kuò)展中實(shí)現(xiàn)的C API列表如下:

復(fù)制代碼 代碼如下:


FILE *fopen(const char *path, const char *mode);
int fclose(FILE *stream);
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
int feof(FILE *stream);



我們實(shí)現(xiàn)這些函數(shù),使它們?cè)诿?xí)慣和簡(jiǎn)單性上符合PHP腳本。如果你曾經(jīng)向PHP社區(qū)貢獻(xiàn)過(guò)代碼,你被期望遵循一些公共習(xí)俗,而不是跟隨C庫(kù)里的API。并不是所有的習(xí)俗都寫(xiě)在PHP代碼樹(shù)的CODING_STANDARDS文件里。這即是說(shuō),此功能已經(jīng)從PHP發(fā)展的很早階段即被包含在PHP中,并且與C庫(kù)API類(lèi)似。PHP安裝已經(jīng)支持fopen(), fclose()和更多的PHP函數(shù)。
以下是PHP風(fēng)格的API:

復(fù)制代碼 代碼如下:


resource file_open(string filename, string mode)
file_open() //接收兩個(gè)字符串(文件名和模式),返回一個(gè)文件的資源句柄。
bool file_close(resource filehandle)
file_close() //接收一個(gè)資源句柄,返回真/假指示是否操作成功。
string file_read(resource filehandle, int size)
file_read() //接收一個(gè)資源句柄和讀入的總字節(jié)數(shù),返回讀入的字符串。
bool file_write(resource filehandle, string buffer)
file_write() //接收一個(gè)資源句柄和被寫(xiě)入的字符串,返回真/假指示是否操作成功。
bool file_eof(resource filehandle)
file_eof() //接收一個(gè)資源句柄,返回真/假指示是否到達(dá)文件的尾部。



因此,我們的函數(shù)定義文件——保存為ext/目錄下的myfile.def——內(nèi)容如下:

復(fù)制代碼 代碼如下:


resource file_open(string filename, string mode)

bool file_close(resource filehandle)

string file_read(resource filehandle, int size)

bool file_write(resource filehandle, string buffer)

bool file_eof(resource filehandle)


下一步,利用ext_skel腳本在ext./ 原代碼目錄執(zhí)行下面的命令:

復(fù)制代碼 代碼如下:


./ext_skel --extname=myfile --proto=myfile.de


然后,按照前一個(gè)例子的關(guān)于編譯新建立腳本的步驟操作。你會(huì)得到一些包含F(xiàn)ETCH_RESOURCE()宏行的編譯錯(cuò)誤,這樣骨架腳本就無(wú)法順利完成編譯。為了讓骨架擴(kuò)展順利通過(guò)編譯,把那些出錯(cuò)行[3]注釋掉即可。

資源
資源是一個(gè)能容納任何信息的抽象數(shù)據(jù)結(jié)構(gòu)。正如前面提到的,這個(gè)信息通常包括例如文件句柄、數(shù)據(jù)庫(kù)連接結(jié)構(gòu)和其他一些復(fù)雜類(lèi)型的數(shù)據(jù)。

使用資源的主要原因是因?yàn)椋嘿Y源被一個(gè)集中的隊(duì)列所管理,該隊(duì)列可以在PHP開(kāi)發(fā)人員沒(méi)有在腳本里面顯式地釋放時(shí)可以自動(dòng)地被釋放。

舉個(gè)例子,考慮到編寫(xiě)一個(gè)腳本,在腳本里調(diào)用mysql_connect()打開(kāi)一個(gè)MySQL連接,可是當(dāng)該數(shù)據(jù)庫(kù)連接資源不再使用時(shí)卻沒(méi)有調(diào)用mysql_close()。在PHP里,資源機(jī)制能夠檢測(cè)什么時(shí)候這個(gè)資源應(yīng)當(dāng)被釋放,然后在當(dāng)前請(qǐng)求的結(jié)尾或通常情況下更早地釋放資源。這就為減少內(nèi)存泄漏賦予了一個(gè)“防彈”機(jī)制。如果沒(méi)有這樣一個(gè)機(jī)制,經(jīng)過(guò)幾次web請(qǐng)求后,web服務(wù)器也許會(huì)潛在地泄漏許多內(nèi)存資源,從而導(dǎo)致服務(wù)器當(dāng)機(jī)或出錯(cuò)。

注冊(cè)資源類(lèi)型
如何使用資源?Zend引擎讓使用資源變地非常容易。你要做的第一件事就是把資源注冊(cè)到引擎中去。使用這個(gè)API函數(shù):

int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld, char *type_name, int module_number)

這個(gè)函數(shù)返回一個(gè)資源類(lèi)型id,該id應(yīng)當(dāng)被作為全局變量保存在擴(kuò)展里,以便在必要的時(shí)候傳遞給其他資源API。ld:該資源釋放時(shí)調(diào)用的函數(shù)。pld用于在不同請(qǐng)求中始終存在的永久資源,本章不會(huì)涉及。type_name是一個(gè)具有描述性類(lèi)型名稱(chēng)的字符串,module_number為引擎內(nèi)部使用,當(dāng)我們調(diào)用這個(gè)函數(shù)時(shí),我們只需要傳遞一個(gè)已經(jīng)定義好的module_number變量。

回到我們的例子中來(lái):我們會(huì)添加下面的代碼到myfile.c原文件中。該文件包括了資源釋放函數(shù)的定義,此資源函數(shù)被傳遞給zend_register_list_destructors_ex()注冊(cè)函數(shù)(資源釋放函數(shù)應(yīng)該提早添加到文件中,以便在調(diào)用zend_register_list_destructors_ex()時(shí)該函數(shù)已被定義):

復(fù)制代碼 代碼如下:


static void myfile_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC){
FILE *fp = (FILE *) rsrc->ptr;
fclose(fp);
}


把注冊(cè)行添加到PHP_MINIT_FUNCTION()后,看起來(lái)應(yīng)該如下面的代碼:

復(fù)制代碼 代碼如下:


PHP_MINIT_FUNCTION(myfile){
/* If you have INI entries, uncomment these lines
ZEND_INIT_MODULE_GLOBALS(myfile, php_myfile_init_globals,NULL);

REGISTER_INI_ENTRIES();
*/

le_myfile = zend_register_list_destructors_ex(myfile_dtor,NULL,"standard-c-file", module_number);

return SUCCESS;
}


l 注意到le_myfile是一個(gè)已經(jīng)被ext_skel腳本定義好的全局變量。

PHP_MINIT_FUNCTION()是一個(gè)先于模塊(擴(kuò)展)的啟動(dòng)函數(shù),是暴露給擴(kuò)展的一部分API。下表提供可用函數(shù)簡(jiǎn)要的說(shuō)明。

函數(shù)聲明宏語(yǔ)義
PHP_MINIT_FUNCTION()當(dāng)PHP被裝載時(shí),模塊啟動(dòng)函數(shù)即被引擎調(diào)用。這使得引擎做一些例如資源類(lèi)型,注冊(cè)INI變量等的一次初始化。
PHP_MSHUTDOWN_FUNCTION()當(dāng)PHP完全關(guān)閉時(shí),模塊關(guān)閉函數(shù)即被引擎調(diào)用。通常用于注銷(xiāo)INI條目
PHP_RINIT_FUNCTION()在每次PHP請(qǐng)求開(kāi)始,請(qǐng)求前啟動(dòng)函數(shù)被調(diào)用。通常用于管理請(qǐng)求前邏輯。
PHP_RSHUTDOWN_FUNCTION()在每次PHP請(qǐng)求結(jié)束后,請(qǐng)求前關(guān)閉函數(shù)被調(diào)用。經(jīng)常應(yīng)用在清理請(qǐng)求前啟動(dòng)函數(shù)的邏輯。
PHP_MINFO_FUNCTION()調(diào)用phpinfo()時(shí)模塊信息函數(shù)被呼叫,從而打印出模塊信息。

新建和注冊(cè)新資源 我們準(zhǔn)備實(shí)現(xiàn)file_open()函數(shù)。當(dāng)我們打開(kāi)文件得到一個(gè)FILE *,我們需要利用資源機(jī)制注冊(cè)它。下面的主要宏實(shí)現(xiàn)注冊(cè)功能:

復(fù)制代碼 代碼如下:


ZEND_REGISTER_RESOURCE(rsrc_result, rsrc_pointer, rsrc_type);


參考表格對(duì)宏參數(shù)的解釋

ZEND_REGISTER_RESOURCE 宏參數(shù)

宏參數(shù)參數(shù)類(lèi)型
rsrc_resultzval *, which should be set with the registered resource information. zval * 設(shè)置為已注冊(cè)資源信息
rsrc_pointerPointer to our resource data. 資源數(shù)據(jù)指針
rsrc_typeThe resource id obtained when registering the resource type. 注冊(cè)資源類(lèi)型時(shí)獲得的資源id


文件函數(shù)
現(xiàn)在你知道了如何使用ZEND_REGISTER_RESOURCE()宏,并且準(zhǔn)備好了開(kāi)始編寫(xiě)file_open()函數(shù)。還有一個(gè)主題我們需要講述。

當(dāng)PHP運(yùn)行在多線程服務(wù)器上,不能使用標(biāo)準(zhǔn)的C文件存取函數(shù)。這是因?yàn)樵谝粋€(gè)線程里正在運(yùn)行的PHP腳本會(huì)改變當(dāng)前工作目錄,因此另外一個(gè)線程里的腳本使用相對(duì)路徑則無(wú)法打開(kāi)目標(biāo)文件。為了阻止這種錯(cuò)誤發(fā)生,PHP框架提供了稱(chēng)作VCWD (virtual current working directory 虛擬當(dāng)前工作目錄)宏,用來(lái)代替任何依賴當(dāng)前工作目錄的存取函數(shù)。這些宏與被替代的函數(shù)具備同樣的功能,同時(shí)是被透明地處理。在某些沒(méi)有標(biāo)準(zhǔn)C函數(shù)庫(kù)平臺(tái)的情況下,VCWD框架則不會(huì)得到支持。例如,Win32下不存在chown(),就不會(huì)有相應(yīng)的VCWD_CHOWN()宏被定義。

VCWD列表

標(biāo)準(zhǔn)C庫(kù)VCWD宏
getcwd()VCWD_GETCWD()
fopen()VCWD_FOPEN
open()VCWD_OPEN() //用于兩個(gè)參數(shù)的版本
open()VCWD_OPEN_MODE() //用于三個(gè)參數(shù)的open()版本
creat()VCWD_CREAT()
chdir()VCWD_CHDIR()
getwd()VCWD_GETWD()
realpath()VCWD_REALPATH()
rename()VCWD_RENAME()
stat()VCWD_STAT()
lstat()VCWD_LSTAT()
unlink()VCWD_UNLINK()
mkdir()VCWD_MKDIR()
rmdir()VCWD_RMDIR()
opendir()VCWD_OPENDIR()
popen()VCWD_POPEN()
access()VCWD_ACCESS()
utime()VCWD_UTIME()
chmod()VCWD_CHMOD()
chown()VCWD_CHOWN()


編寫(xiě)利用資源的第一個(gè)PHP函數(shù)
實(shí)現(xiàn)file_open()應(yīng)該非常簡(jiǎn)單,看起來(lái)像下面的樣子:

復(fù)制代碼 代碼如下:


PHP_FUNCTION(file_open){
char *filename = NULL;
char *mode = NULL;
int argc = ZEND_NUM_ARGS();
int filename_len;
int mode_len;
FILE *fp;
if (zend_parse_parameters(argc TSRMLS_CC, "ss", &filename,&filename_len, &mode, &mode_len) == FAILURE) {
return;
}
fp = VCWD_FOPEN(filename, mode);
if (fp == NULL) {
RETURN_FALSE;
}
ZEND_REGISTER_RESOURCE(return_value, fp, le_myfile);
}


你可能會(huì)注意到資源注冊(cè)宏的第一個(gè)參數(shù)return_value,可此地找不到它的定義。這個(gè)變量自動(dòng)的被擴(kuò)展框架定義為zval * 類(lèi)型的函數(shù)返回值。先前討論的、能夠影響返回值的RETURN_LONG() 和RETVAL_BOOL()宏確實(shí)改變了return_value的值。因此很容易猜到程序注冊(cè)了我們?nèi)〉玫奈募羔榝p,同時(shí)設(shè)置return_value為該注冊(cè)資源。

訪問(wèn)資源 需要使用下面的宏訪問(wèn)資源(參看表對(duì)宏參數(shù)的解釋?zhuān)?

復(fù)制代碼 代碼如下:


ZEND_FETCH_RESOURCE(rsrc, rsrc_type, passed_id, default_id, resource_type_name, resource_type);


ZEND_FETCH_RESOURCE 宏參數(shù)

參數(shù)含義
rsrc資源值保存到的變量名。它應(yīng)該和資源有相同類(lèi)型。
rsrc_typersrc的類(lèi)型,用于在內(nèi)部把資源轉(zhuǎn)換成正確的類(lèi)型
passed_id尋找的資源值(例如zval **)
default_id如果該值不為-1,就使用這個(gè)id。用于實(shí)現(xiàn)資源的默認(rèn)值。
resource_type_name資源的一個(gè)簡(jiǎn)短名稱(chēng),用于錯(cuò)誤信息。
resource_type注冊(cè)資源的資源類(lèi)型id


使用這個(gè)宏,我們現(xiàn)在能夠?qū)崿F(xiàn)file_eof():

復(fù)制代碼 代碼如下:


PHP_FUNCTION(file_eof){
int argc = ZEND_NUM_ARGS();
zval *filehandle = NULL;
FILE *fp;
if (zend_parse_parameters(argc TSRMLS_CC, "r", &filehandle) ==FAILURE) {
return;
}
ZEND_FETCH_RESOURCE(fp, FILE *, &filehandle, -1, "standard-c-file",le_myfile);
if (fp == NULL){
RETURN_FALSE;
}
if (feof(fp) <= 0) {
/* Return eof also if there was an error */
RETURN_TRUE;
}
RETURN_FALSE;
}


刪除一個(gè)資源通常使用下面這個(gè)宏刪除一個(gè)資源:

復(fù)制代碼 代碼如下:


int zend_list_delete(int id)


傳遞給宏一個(gè)資源id,返回SUCCESS或者FAILURE。如果資源存在,優(yōu)先從Zend資源列隊(duì)中刪除,該過(guò)程中會(huì)調(diào)用該資源類(lèi)型的已注冊(cè)資源清理函數(shù)。因此,在我們的例子中,不必取得文件指針,調(diào)用fclose()關(guān)閉文件,然后再刪除資源。直接把資源刪除掉即可。
使用這個(gè)宏,我們能夠?qū)崿F(xiàn)file_close():

復(fù)制代碼 代碼如下:


PHP_FUNCTION(file_close){
int argc = ZEND_NUM_ARGS();
zval *filehandle = NULL;
if (zend_parse_parameters(argc TSRMLS_CC, "r", &filehandle) == FAILURE) {
return;
}
if (zend_list_delete(Z_RESVAL_P(filehandle)) == FAILURE) {
RETURN_FALSE;
}
RETURN_TRUE;
}


你肯定會(huì)問(wèn)自己Z_RESVAL_P()是做什么的。當(dāng)我們使用zend_parse_parameters()從參數(shù)列表中取得資源的時(shí)候,得到的是zval的形式。為了獲得資源id,我們使用Z_RESVAL_P()宏得到id,然后把id傳遞給zend_list_delete()。
有一系列宏用于訪問(wèn)存儲(chǔ)于zval值(參考表的宏列表)。盡管在大多數(shù)情況下zend_parse_parameters()返回與c類(lèi)型相應(yīng)的值,我們?nèi)韵M苯犹幚韟val,包括資源這一情況。

Zval訪問(wèn)宏

訪問(wèn)對(duì)象C 類(lèi)型
Z_LVAL, Z_LVAL_P, Z_LVAL_PP整型值long
Z_BVAL, Z_BVAL_P, Z_BVAL_PP布爾值zend_bool
Z_DVAL, Z_DVAL_P, Z_DVAL_PP浮點(diǎn)值double
Z_STRVAL, Z_STRVAL_P, Z_STRVAL_PP字符串值char *
Z_STRLEN, Z_STRLEN_P, Z_STRLEN_PP字符串長(zhǎng)度值int
Z_RESVAL, Z_RESVAL_P,Z_RESVAL_PP資源值long
Z_ARRVAL, Z_ARRVAL_P, Z_ARRVAL_PP聯(lián)合數(shù)組HashTable *
Z_TYPE, Z_TYPE_P, Z_TYPE_PPZval類(lèi)型Enumeration (IS_NULL, IS_LONG, IS_DOUBLE, IS_STRING, IS_ARRAY, IS_OBJECT, IS_BOOL, IS_RESOURCE)
Z_OBJPROP, Z_OBJPROP_P, Z_OBJPROP_PP對(duì)象屬性hash(本章不會(huì)談到)HashTable *
Z_OBJCE, Z_OBJCE_P, Z_OBJCE_PP對(duì)象的類(lèi)信息zend_class_entry

用于訪問(wèn)zval值的宏

所有的宏都有三種形式:一個(gè)是接受zval s,另外一個(gè)接受zval *s,最后一個(gè)接受zval **s。它們的區(qū)別是在命名上,第一個(gè)沒(méi)有后綴,zval *有后綴_P(代表一個(gè)指針),最后一個(gè) zval **有后綴_PP(代表兩個(gè)指針)。
現(xiàn)在,你有足夠的信息來(lái)獨(dú)立完成 file_read()和 file_write()函數(shù)。這里是一個(gè)可能的實(shí)現(xiàn):

復(fù)制代碼 代碼如下:


PHP_FUNCTION(file_read){
int argc = ZEND_NUM_ARGS();
long size;
zval *filehandle = NULL;
FILE *fp;
char *result;
size_t bytes_read;
if (zend_parse_parameters(argc TSRMLS_CC, "rl", &filehandle,&size) == FAILURE) {
return;
}
ZEND_FETCH_RESOURCE(fp, FILE *, &filehandle, -1, "standard-cfile", le_myfile);
result = (char *) emalloc(size+1);
bytes_read = fread(result, 1, size, fp);
result[bytes_read] = '\0';
RETURN_STRING(result, 0);
}
PHP_FUNCTION(file_write){
char *buffer = NULL;
int argc = ZEND_NUM_ARGS();
int buffer_len;
zval *filehandle = NULL;
FILE *fp;
if (zend_parse_parameters(argc TSRMLS_CC, "rs", &filehandle,&buffer, &buffer_len) == FAILURE) {
return;
}
ZEND_FETCH_RESOURCE(fp, FILE *, &filehandle, -1, "standard-cfile", le_myfile);
if (fwrite(buffer, 1, buffer_len, fp) != buffer_len) {
RETURN_FALSE;
}
RETURN_TRUE;
}


測(cè)試擴(kuò)展
你現(xiàn)在可以編寫(xiě)一個(gè)測(cè)試腳本來(lái)檢測(cè)擴(kuò)展是否工作正常。下面是一個(gè)示例腳本,該腳本打開(kāi)文件test.txt,輸出文件類(lèi)容到標(biāo)準(zhǔn)輸出,建立一個(gè)拷貝test.txt.new。

復(fù)制代碼 代碼如下:


<?php
$fp_in = file_open("test.txt", "r") or die("Unable to open input file\n");
$fp_out = file_open("test.txt.new", "w") or die("Unable to open output file\n");
while (!file_eof($fp_in)) {
$str = file_read($fp_in, 1024);
print($str);
file_write($fp_out, $str);
}
file_close($fp_in);
file_close($fp_out);
?>


全局變量
你可能希望在擴(kuò)展里使用全局C變量,無(wú)論是獨(dú)自在內(nèi)部使用或訪問(wèn)php.ini文件中的INI擴(kuò)展注冊(cè)標(biāo)記(INI在下一節(jié)中討論)。因?yàn)镻HP是為多線程環(huán)境而設(shè)計(jì),所以不必定義全局變量。PHP提供了一個(gè)創(chuàng)建全局變量的機(jī)制,可以同時(shí)應(yīng)用在線程和非線程環(huán)境中。我們應(yīng)當(dāng)始終利用這個(gè)機(jī)制,而不要自主地定義全局變量。用一個(gè)宏訪問(wèn)這些全局變量,使用起來(lái)就像普通全局變量一樣。

用于生成myfile工程骨架文件的ext_skel腳本創(chuàng)建了必要的代碼來(lái)支持全局變量。通過(guò)檢查php_myfile.h文件,你應(yīng)當(dāng)發(fā)現(xiàn)類(lèi)似下面的被注釋掉的一節(jié),

復(fù)制代碼 代碼如下:


ZEND_BEGIN_MODULE_GLOBALS(myfile)
int global_value;
char *global_string;
ZEND_END_MODULE_GLOBALS(myfile)


你可以把這一節(jié)的注釋去掉,同時(shí)添加任何其他全局變量于這兩個(gè)宏之間。文件后部的幾行,骨架腳本自動(dòng)地定義一個(gè)MYFILE_G(v)宏。這個(gè)宏應(yīng)當(dāng)被用于所有的代碼,以便訪問(wèn)這些全局變量。這就確保在多線程環(huán)境中,訪問(wèn)的全局變量?jī)H是一個(gè)線程的拷貝,而不需要互斥的操作。

為了使全局變量有效,最后需要做的是把myfile.c:

復(fù)制代碼 代碼如下:


ZEND_DECLARE_MODULE_GLOBALS(myfile)


注釋去掉。

你也許希望在每次PHP請(qǐng)求的開(kāi)始初始化全局變量。另外,做為一個(gè)例子,全局變量已指向了一個(gè)已分配的內(nèi)存,在每次PHP請(qǐng)求結(jié)束時(shí)需要釋放內(nèi)存。為了達(dá)到這些目的,全局變量機(jī)制提供了一個(gè)特殊的宏,用于注冊(cè)全局變量的構(gòu)造和析構(gòu)函數(shù)(參考表對(duì)宏參數(shù)的說(shuō)明):

復(fù)制代碼 代碼如下:


ZEND_INIT_MODULE_GLOBALS(module_name, globals_ctor, globals_dtor)


表 ZEND_INIT_MODULE_GLOBALS 宏參數(shù)

參數(shù)含義
module_name與傳遞給ZEND_BEGIN_MODULE_GLOBALS()宏相同的擴(kuò)展名稱(chēng)。
globals_ctor構(gòu)造函數(shù)指針。在myfile擴(kuò)展里,函數(shù)原形與void php_myfile_init_globals(zend_myfile_globals *myfile_globals)類(lèi)似
globals_dtor析構(gòu)函數(shù)指針。例如,php_myfile_init_globals(zend_myfile_globals *myfile_globals)


你可以在myfile.c里看到如何使用構(gòu)造函數(shù)和ZEND_INIT_MODULE_GLOBALS()宏的示例。

添加自定義INI指令
INI文件(php.ini)的實(shí)現(xiàn)使得PHP擴(kuò)展注冊(cè)和監(jiān)聽(tīng)各自的INI條目。如果這些INI條目由php.ini、Apache的htaccess或其他配置方法來(lái)賦值,注冊(cè)的INI變量總是更新到正確的值。整個(gè)INI框架有許多不同的選項(xiàng)以實(shí)現(xiàn)其靈活性。我們涉及一些基本的(也是個(gè)好的開(kāi)端),借助本章的其他材料,我們就能夠應(yīng)付日常開(kāi)發(fā)工作的需要。

通過(guò)在PHP_INI_BEGIN()/PHP_INI_END()宏之間的STD_PHP_INI_ENTRY()宏注冊(cè)PHP INI指令。例如在我們的例子里,myfile.c中的注冊(cè)過(guò)程應(yīng)當(dāng)如下:

復(fù)制代碼 代碼如下:


PHP_INI_BEGIN()
STD_PHP_INI_ENTRY("myfile.global_value", "42", PHP_INI_ALL, OnUpdateInt, global_value, zend_myfile_globals, myfile_globals)
STD_PHP_INI_ENTRY("myfile.global_string", "foobar", PHP_INI_ALL, OnUpdateString, global_string, zend_myfile_globals, myfile_globals)
PHP_INI_END()



除了STD_PHP_INI_ENTRY()其他宏也能夠使用,但這個(gè)宏是最常用的,可以滿足大多數(shù)需要(參看表對(duì)宏參數(shù)的說(shuō)明):

復(fù)制代碼 代碼如下:


STD_PHP_INI_ENTRY(name, default_value, modifiable, on_modify, property_name, struct_type, struct_ptr)


STD_PHP_INI_ENTRY 宏參數(shù)表

參數(shù)含義
nameINI條目名
default_value如果沒(méi)有在INI文件中指定,條目的默認(rèn)值。默認(rèn)值始終是一個(gè)字符串。
modifiable設(shè)定在何種環(huán)境下INI條目可以被更改的位域??梢缘闹凳牵?br/>? PHP_INI_SYSTEM. 能夠在php.ini或http.conf等系統(tǒng)文件更改
? PHP_INI_PERDIR. 能夠在 .htaccess中更改
? PHP_INI_USER. 能夠被用戶腳本更改
? PHP_INI_ALL. 能夠在所有地方更改
on_modify處理INI條目更改的回調(diào)函數(shù)。你不需自己編寫(xiě)處理程序,使用下面提供的函數(shù)。包括:
? OnUpdateInt
? OnUpdateString
? OnUpdateBool
? OnUpdateStringUnempty
? OnUpdateReal
property_name應(yīng)當(dāng)被更新的變量名
struct_type變量駐留的結(jié)構(gòu)類(lèi)型。因?yàn)橥ǔJ褂萌肿兞繖C(jī)制,所以這個(gè)類(lèi)型自動(dòng)被定義,類(lèi)似于zend_myfile_globals。
struct_ptr全局結(jié)構(gòu)名。如果使用全局變量機(jī)制,該名為myfile_globals。


最后,為了使自定義INI條目機(jī)制正常工作,你需要分別去掉PHP_MINIT_FUNCTION(myfile)中的REGISTER_INI_ENTRIES()調(diào)用和PHP_MSHUTDOWN_FUNCTION(myfile)中的UNREGISTER_INI_ENTRIES()的注釋。

訪問(wèn)兩個(gè)示例全局變量中的一個(gè)與在擴(kuò)展里編寫(xiě)MYFILE_G(global_value) 和MYFILE_G(global_string)一樣簡(jiǎn)單。

如果你把下面的兩行放在php.ini中,MYFILE_G(global_value)的值會(huì)變?yōu)?9。

復(fù)制代碼 代碼如下:


; php.ini – The following line sets the INI entry myfile.global_value to 99.myfile.global_value = 9


線程安全資源管理宏
現(xiàn)在,你肯定注意到以TSRM(線程安全資源管理器)開(kāi)頭的宏隨處使用。這些宏提供給擴(kuò)展擁有獨(dú)自的全局變量的可能,正如前面提到的。

當(dāng)編寫(xiě)PHP擴(kuò)展時(shí),無(wú)論是在多進(jìn)程或多線程環(huán)境中,都是依靠這一機(jī)制訪問(wèn)擴(kuò)展自己的全局變量。如果使用全局變量訪問(wèn)宏(例如MYFILE_G()宏),需要確保TSRM上下文信息出現(xiàn)在當(dāng)前函數(shù)中?;谛阅艿脑?,Zend引擎試圖把這個(gè)上下文信息作為參數(shù)傳遞到更多的地方,包括PHP_FUNCTION()的定義。正因?yàn)檫@樣,在PHP_FUNCTION()內(nèi)當(dāng)編寫(xiě)的代碼使用訪問(wèn)宏(例如MYFILE_G()宏)時(shí),不需要做任何特殊的聲明。然而,如果PHP函數(shù)調(diào)用其他需要訪問(wèn)全局變量的C函數(shù),要么把上下文作為一個(gè)額外的參數(shù)傳遞給C函數(shù),要么提取上下文(要慢點(diǎn))。

在需要訪問(wèn)全局變量的代碼塊開(kāi)頭使用TSRMLS_FETCH()來(lái)提取上下文。例如:

復(fù)制代碼 代碼如下:


void myfunc(){
TSRMLS_FETCH();

MYFILE_G(myglobal) = 2;
}


如果希望讓代碼更加優(yōu)化,更好的辦法是直接傳遞上下文給函數(shù)(正如前面敘述的,PHP_FUNCTION()范圍內(nèi)自動(dòng)可用)??梢允褂肨SRMLS_C(C表示調(diào)用Call)和TSRMLS_CC(CC邊式調(diào)用Call和逗號(hào)Comma)宏。前者應(yīng)當(dāng)用于僅當(dāng)上下文作為一個(gè)單獨(dú)的參數(shù),后者應(yīng)用于接受多個(gè)參數(shù)的函數(shù)。在后一種情況中,因?yàn)楦鶕?jù)取名,逗號(hào)在上下文的前面,所以TSRMLS_CC不能是第一個(gè)函數(shù)參。

在函數(shù)原形中,可以分別使用TSRMLS_D和TSRMLS_DC宏聲名正在接收上下文。

下面是前一例子的重寫(xiě),利用了參數(shù)傳遞上下文。

復(fù)制代碼 代碼如下:


void myfunc(TSRMLS_D){
MYFILE_G(myglobal) = 2;
}
PHP_FUNCTION(my_php_function)
{

myfunc(TSRMLS_C);

}
~


總 結(jié)
現(xiàn)在,你已經(jīng)學(xué)到了足夠的東西來(lái)創(chuàng)建自己的擴(kuò)展。本章講述了一些重要的基礎(chǔ)來(lái)編寫(xiě)和理解PHP擴(kuò)展。Zend引擎提供的擴(kuò)展API相當(dāng)豐富,使你能夠開(kāi)發(fā)面向?qū)ο蟮臄U(kuò)展。幾乎沒(méi)有文檔談幾許多高級(jí)特性。當(dāng)然,依靠本章所學(xué)的基礎(chǔ)知識(shí),你可以通過(guò)瀏覽現(xiàn)有的原碼學(xué)到很多。

更多關(guān)于信息可以在PHP手冊(cè)的擴(kuò)展PHP章節(jié)http://www.php.net/manual/en/zend.php中找到。另外,你也可以考慮加入PHP開(kāi)發(fā)者郵件列表internals@ lists.php.net,該郵件列表圍繞開(kāi)發(fā)PHP 本身。你還可以查看一下新的擴(kuò)展生成工具——PECL_Gen(http://pear.php.net/package/PECL_Gen),這個(gè)工具正在開(kāi)發(fā)之中,比起本章使用的ext_skel有更多的特性。

此外你還可以關(guān)注風(fēng)雪之隅, 會(huì)有更多相關(guān)知識(shí)更新.

詞匯表

binary safe二進(jìn)制安全
context上下文
extensions擴(kuò)展
entry條目
skeleton骨架
Thread-Safe Resource Manager TSRM線程安全資源管理器


[1] 可參考譯者寫(xiě)的
[2] 譯者:可以使用phpcli程序在控制臺(tái)里執(zhí)行php文件。
[3] 譯者:可以查看到生成的FETCH_RESOURCE()宏參數(shù)是一些'???'。

到此,關(guān)于“怎么用C/C++擴(kuò)展你的PHP 為你的php增加功能”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

php
AI