php调用C代码的方法详解和zend_parse_parameters函数详解
來源:http://my.oschina.net/Customs/blog/490873
http://blog.csdn.net/super_ufo/article/details/3863731
?
?
php調(diào)用C代碼的方法詳解
在php程序中需要用到C代碼,應(yīng)該是下面兩種情況:
1 已有C代碼,在php程序中想直接用 2 由于php的性能問題,需要用C來實(shí)現(xiàn)部分功能針對第一種情況,最合適的方法是用system調(diào)用,把現(xiàn)有C代碼寫成一個獨(dú)立的程序。參數(shù)通過命令行或者標(biāo)準(zhǔn)輸入傳入,結(jié)果從標(biāo)準(zhǔn)輸出讀出。其次,稍麻煩一點(diǎn)的方法是C代碼寫成一個daemon,php程序用socket來和它進(jìn)行通訊。
重點(diǎn)講講第二種情況,雖然沿用system調(diào)用的方法也可以,但是想想你的目的是優(yōu)化性能,那么頻繁的起這么多進(jìn)程,當(dāng)然會讓性能下降。而寫daemon的方法固然可行,可是繁瑣了很多。
我的簡單測試,同樣一個算法,用C來寫比用php效率能提高500倍。而用php擴(kuò)展的方式,也能提高90多倍(其中的性能損失在了參數(shù)傳遞上了吧,我猜)。
所以有些時候php擴(kuò)展就是我們的最佳選擇了。
這里我著重介紹一下用C寫php擴(kuò)展的方法,而且不需要重新編譯php。
首先,找到一個php的源碼,php4或者php5版本的都可以,與你目標(biāo)平臺的php版本沒有關(guān)系。
在源碼的ext目錄下可以找到名為ext_skel的腳本(windows平臺使用ext_skel_win32.php) 在這個目錄下執(zhí)行./ext_skel?--extname=hello(我用hello作為例子) 這時生成了一個目錄 hello,目錄下有幾個文件,你只需要關(guān)心這三個:config.m4?hello.c php_hello.h
把這個目錄拷備到任何你希望的地方,cd進(jìn)去,依次執(zhí)行 (安裝phpize等工具 yum -y install php-devel ) phpize ./configure make 什么也沒發(fā)生,對吧? 這是因?yàn)槁┝艘徊?#xff0c;打開config.m4,找到下面 dnl If your extension references something external, use with:
... dnl Otherwise use enable:
... 這是讓你選擇你的擴(kuò)展使用with還是enable,我們用with吧。把with那一部分取消注釋。 如果你和我一樣使用vim編輯器,你就會很容易發(fā)現(xiàn)dnl三個字母原來是表示注釋的呀(這是因?yàn)関im默認(rèn)帶了各種文件格式的語法著色包)
我們修改了config.m4后,繼續(xù) phpize ./configure make 這時,modules下面會生成hello.so和hello.la文件。一個是動態(tài)庫,一個是靜態(tài)庫。
你的php擴(kuò)展已經(jīng)做好了,盡管它還沒有實(shí)現(xiàn)你要的功能,我先說說怎么使用這個擴(kuò)展吧!ext_skel為你生成了一個hello.php里面有調(diào)用示例,但是那個例子需要你把hello.so拷貝到php的擴(kuò)展目錄中去,我們只想實(shí)現(xiàn)自己的功能,不想打造山寨版php,改用我下面的方法來加載吧:
隨后一個讓人關(guān)心的問題是,如何添加函數(shù)、實(shí)現(xiàn)參數(shù)傳遞和返回值
添加函數(shù)步驟如下: php_hello.h: PHP_FUNCTION(confirm_hello_compiled);// 括號里面填寫函數(shù)名
hello.c zend_function_entry hello_functions[] = { ?? ?PHP_FE(confirm_hello_compiled, ?NULL) ? ? ? /* 這里添加一行 */ ?? ?{NULL, NULL, NULL} ?/* Must be the last line in hello_functions[] */ }; PHP_FUNCTION(confirm_hello_compiled)? {// 這里寫函數(shù)體 } 要實(shí)現(xiàn)的函數(shù)原型其實(shí)都一個樣,用宏P(guān)HP_FUNCTION來包裝了一下,另外呢,在hello_functions里面添加了一行信息,表示你這個模塊中有這個函數(shù)了。
那么都是一樣的函數(shù)原型,如何區(qū)分返回值與參數(shù)呢? 我給一個例子:
把這個當(dāng)成是scanf來理解好了。 類型說明見下表:
| Boolean | b | zend_bool |
| Long | l | long |
| Double | d | double |
| String | s | char*, int |
| Resource | r | zval* |
| Array | a | zval* |
| Object | o | zval* |
| zval | z | zval* |
那么返回值怎么辦呢? 使用下面一組宏來表示: RETURN_STRING
RETURN_LONG
RETURN_DOUBLE
RETURN_BOOL
RETURN_NULL
注意RETURN_STRING有兩個參數(shù) 當(dāng)你需要復(fù)制一份字符串時使用 RETURN_STRING("Hello World", 1);
否則使用 RETURN_STRING(str, 0);
這里涉及到了模塊中內(nèi)存的分配,當(dāng)你申請的內(nèi)存需要php程序中去釋放的話,請參照如下表
| malloc(count) calloc(count, num) | emalloc(count) ecalloc(count, num) | pemalloc(count, 1)* pecalloc(count, num, 1) |
| strdup(str) strndup(str, len) | estrdup(str) estrndup(str, len) | pestrdup(str, 1) pemalloc() & memcpy() |
| free(ptr) | efree(ptr) | pefree(ptr, 1) |
| realloc(ptr, newsize) | erealloc(ptr, newsize) | perealloc(ptr, newsize, 1) |
| malloc(count * num + extr)** | safe_emalloc(count, num, extr) | safe_pemalloc(count, num, extr) |
基本上就是這樣,可以開始寫一個php的擴(kuò)展了。 從我目前的應(yīng)用來看,能操縱字符串就夠用了,所以我就只能介紹這么多了,如果要詳細(xì)一點(diǎn)的呢,例如php數(shù)組怎么處理,可以參考 http://devzone.zend.com/node/view/id/1022 翻譯:http://blog.csdn.net/alexdream/archive/2008/03/24/2213344.aspx
更好的文章:http://www.toplee.com/blog/56.html#pp1
本節(jié)沒有介紹關(guān)于腳本引擎基本構(gòu)造的一些知識,而是直接進(jìn)入擴(kuò)展的編碼講解中,因此不要擔(dān)心你無法立刻獲得對擴(kuò)展整體把握的感覺。假設(shè)你正在開發(fā)一個網(wǎng)站,需要一個把字符串重復(fù)n次的函數(shù)。下面是用PHP寫的例子:
?
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è)由于一些奇怪的原因,你需要時常調(diào)用這個函數(shù),而且還要傳給函數(shù)很長的字符串和大值n。這意味著在腳本里有相當(dāng)巨大的字符串連接量和內(nèi)存重新分配過程,以至顯著地降低腳本執(zhí)行速度。如果有一個函數(shù)能夠更快地分配大量且足夠的內(nèi)存來存放結(jié)果字符串,然后把$string重復(fù)n次,就不需要在每次循環(huán)迭代中分配內(nèi)存。
為擴(kuò)展建立函數(shù)的第一步是寫一個函數(shù)定義文件,該函數(shù)定義文件定義了擴(kuò)展對外提供的函數(shù)原形。該例中,定義函數(shù)只有一行函數(shù)原形self_concat()?:
?
string?self_concat(string?str,?int?n)
?
函數(shù)定義文件的一般格式是一個函數(shù)一行。你可以定義可選參數(shù)和使用大量的PHP類型,包括:?bool,?float,?int,?array等。
保存為myfunctions.def文件至PHP原代碼目錄樹下。
該是通過擴(kuò)展骨架(skeleton)構(gòu)造器運(yùn)行函數(shù)定義文件的時機(jī)了。該構(gòu)造器腳本叫ext_skel,放在PHP原代碼目錄樹的ext/目錄下(PHP原碼主目錄下的README.EXT_SKEL提供了更多的信息)。假設(shè)你把函數(shù)定義保存在一個叫做myfunctions.def的文件里,而且你希望把擴(kuò)展取名為myfunctions,運(yùn)行下面的命令來建立擴(kuò)展骨架
?
./ext_skel?--extname=myfunctions?--proto=myfunctions.def
?
???????這個命令在ext/目錄下建立了一個myfunctions/目錄。你要做的第一件事情也許就是編譯該骨架,以便編寫和測試實(shí)際的C代碼。編譯擴(kuò)展有兩種方法:
?
???作為一個可裝載模塊或者DSO(動態(tài)共享對象)
???靜態(tài)編譯到PHP
?
因?yàn)榈诙N方法比較容易上手,所以本章采用靜態(tài)編譯。如果你對編譯可裝載擴(kuò)展模塊感興趣,可以閱讀PHP原代碼根目錄下的README.SELF-CONTAINED_EXTENSIONS文件。為了使擴(kuò)展能夠被編譯,需要修改擴(kuò)展目錄ext/myfunctions/下的config.m4文件。擴(kuò)展沒有包裹任何外部的C庫,你需要添加支持--enable-myfunctions配置開關(guān)到PHP編譯系統(tǒng)里(–with-extension?開關(guān)用于那些需要用戶指定相關(guān)C庫路徑的擴(kuò)展)。可以去掉自動生成的下面兩行的注釋來開啟這個配置。
?
PHP_ARG_ENABLE(myfunctions,?whether?to?enable?myfunctions?support,
[?--enable-myfunctions????????????????Include?myfunctions?support])
?
現(xiàn)在剩下的事情就是在PHP原代碼樹根目錄下運(yùn)行./buildconf,該命令會生成一個新的配置腳本。通過查看./configure?--help輸出信息,可以檢查新的配置選項是否被包含到配置文件中。現(xiàn)在,打開你喜好的配置選項開關(guān)和--enable-myfunctions重新配置一下PHP。最后的但不是最次要的是,用make來重新編譯PHP。
???????ext_skel應(yīng)該把兩個PHP函數(shù)添加到你的擴(kuò)展骨架了:打算實(shí)現(xiàn)的self_concat()函數(shù)和用于檢測myfunctions?是否編譯到PHP的confirm_myfunctions_compiled()函數(shù)。完成PHP的擴(kuò)展開發(fā)后,可以把后者去掉。
?
<?php
print?confirm_myfunctions_compiled("myextension");
?>
?
運(yùn)行這個腳本會出現(xiàn)類似下面的輸出:
"Congratulations!?You?have?successfully?modified?ext/myfunctions
config.m4.?Module?myfunctions?is?now?compiled?into?PHP."?
另外,ext_skel腳本生成一個叫myfunctions.php的腳本,你也可以利用它來驗(yàn)證擴(kuò)展是否被成功地編譯到PHP。它會列出該擴(kuò)展所支持的所有函數(shù)。
???????現(xiàn)在你學(xué)會如何編譯擴(kuò)展了,該是真正地研究self_concat()函數(shù)的時候了。
??????????????下面就是ext_skel腳本生成的骨架結(jié)構(gòu):
?
/*?{{{?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");
}
/*?}}}?*/
? ? ? ? ? ? zend_parse_parameters 詳解自動生成的PHP函數(shù)周圍包含了一些注釋,這些注釋用于自動生成代碼文檔和vi、Emacs等編輯器的代碼折疊。函數(shù)自身的定義使用了宏PHP_FUNCTION(),該宏可以生成一個適合于Zend引擎的函數(shù)原型。邏輯本身分成語義各部分,取得調(diào)用函數(shù)的參數(shù)和邏輯本身。
???????為了獲得函數(shù)傳遞的參數(shù),可以使用zend_parse_parameters()API函數(shù)。下面是該函數(shù)的原型:
zend_parse_parameters(int?num_args?TSRMLS_DC,?char?*type_spec,?…);
?
第一個參數(shù)是傳遞給函數(shù)的參數(shù)個數(shù)。通常的做法是傳給它ZEND_NUM_ARGS()。(ZEND_NUM_ARGS()?來表示對傳入的參數(shù)“有多少要多少”)這是一個表示傳遞給函數(shù)參數(shù)總個數(shù)的宏。第二個參數(shù)是為了線程安全,總是傳遞TSRMLS_CC宏,后面會講到。第三個參數(shù)是一個字符串,指定了函數(shù)期望的參數(shù)類型,后面緊跟著需要隨參數(shù)值更新的變量列表。因?yàn)?span style="font-family:'Times New Roman'">PHP采用松散的變量定義和動態(tài)的類型判斷,這樣做就使得把不同類型的參數(shù)轉(zhuǎn)化為期望的類型成為可能。例如,如果用戶傳遞一個整數(shù)變量,可函數(shù)需要一個浮點(diǎn)數(shù),那么zend_parse_parameters()就會自動地把整數(shù)轉(zhuǎn)換為相應(yīng)的浮點(diǎn)數(shù)。如果實(shí)際值無法轉(zhuǎn)換成期望類型(比如整形到數(shù)組形),會觸發(fā)一個警告。
下表列出了可能指定的類型。我們從完整性考慮也列出了一些沒有討論到的類型。
?
| 類型指定符 | 對應(yīng)的C類型 | 描述 |
| l | long | 符號整數(shù) |
| d | double | 浮點(diǎn)數(shù) |
| s | char?*,?int | 二進(jìn)制字符串,長度 |
| b | zend_bool | 邏輯型(1或0) |
| r | zval?* | 資源(文件指針,數(shù)據(jù)庫連接等) |
| a | zval?* | 聯(lián)合數(shù)組 |
| o | zval?* | 任何類型的對象 |
| O | zval?* | 指定類型的對象。需要提供目標(biāo)對象的類類型 |
| z | zval?* | 無任何操作的zval? |
?
為了容易地理解最后幾個選項的含義,你需要知道zval是Zend引擎的值容器[1]。無論這個變量是布爾型,字符串型或者其他任何類型,其信息總會包含在一個zval聯(lián)合體中。本章中我們不直接存取zval,而是通過一些附加的宏來操作。下面的是或多或少在C中的zval,?以便我們能更好地理解接下來的代碼。
?
typedef?union?_zval?{
long?lval;
double?dval;
struct?{
char?*val;
int?len;
}?str;
HashTable?*ht;
zend_object_value?obj;
}?zval;
?
在我們的例子中,我們用基本類型調(diào)用zend_parse_parameters(),以本地C類型的方式取得函數(shù)參數(shù)的值,而不是用zval容器。
為了讓zend_parse_parameters()能夠改變傳遞給它的參數(shù)的值,并返回這個改變值,需要傳遞一個引用。仔細(xì)查看一下self_concat():
?
if?(zend_parse_parameters(argc?TSRMLS_CC,?"sl",?&str,?&str_len,?&n)?==?FAILURE)
return;
?
???????注意到自動生成的代碼會檢測函數(shù)的返回值FAILUER(成功即SUCCESS)來判斷是否成功。如果沒有成功則立即返回,并且由zend_parse_parameters()負(fù)責(zé)觸發(fā)警告信息。因?yàn)楹瘮?shù)打算接收一個字符串l和一個整數(shù)n,所以指定?”sl”?作為其類型指示符。s需要兩個參數(shù),所以我們傳遞參考char?*?和?int?(str?和?str_len)給zend_parse_parameters()函數(shù)。無論什么時候,記得總是在代碼中使用字符串長度str_len來確保函數(shù)工作在二進(jìn)制安全的環(huán)境中。不要使用strlen()和strcpy(),除非你不介意函數(shù)在二進(jìn)制字符串下不能工作。二進(jìn)制字符串是包含有nulls的字符串。二進(jìn)制格式包括圖象文件,壓縮文件,可執(zhí)行文件和更多的其他文件。”l”?只需要一個參數(shù),所以我們傳遞給它n的引用。盡管為了清晰起見,骨架腳本生成的C變量名與在函數(shù)原型定義文件中的參數(shù)名一樣;這樣做不是必須的,盡管在實(shí)踐中鼓勵這樣做。
回到轉(zhuǎn)換規(guī)則中來。下面三個對self_concat()函數(shù)的調(diào)用使str,?str_len和n得到同樣的值:
?
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。
?
在我們編寫代碼來實(shí)現(xiàn)連接字符串返回給PHP的函數(shù)前,還得談?wù)剝蓚€重要的話題:內(nèi)存管理、從PHP內(nèi)部返回函數(shù)值所使用的API!!
?
?
內(nèi)存管理
?
用于從堆中分配內(nèi)存的PHP?API幾乎和標(biāo)準(zhǔn)C?API一樣。在編寫擴(kuò)展的時候,使用下面與C對應(yīng)(因此不必再解釋)的API函數(shù):
?
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沒有strndup()?”是的,這是正確的,因?yàn)镚NU擴(kuò)展通常在Linux下可用。estrndup()只是PHP下的一個特殊函數(shù)。它的行為與estrdup()相似,但是可以指定字符串重復(fù)的次數(shù)(不需要結(jié)束空字符),同時是二進(jìn)制安全的。這是推薦使用estrndup()而不是estrdup()的原因。
在幾乎所有的情況下,你應(yīng)該使用這些內(nèi)存分配函數(shù)。有一些情況,即擴(kuò)展需要分配在請求中永久存在的內(nèi)存,從而不得不使用malloc(),但是除非你知道你在做什么,你應(yīng)該始終使用以上的函數(shù)。如果沒有使用這些內(nèi)存函數(shù),而相反使用標(biāo)準(zhǔn)C函數(shù)分配的內(nèi)存返回給腳本引擎,那么PHP會崩潰。
這些函數(shù)的優(yōu)點(diǎn)是:任何分配的內(nèi)存在偶然情況下如果沒有被釋放,則會在頁面請求的最后被釋放。因此,真正的內(nèi)存泄漏不會產(chǎn)生。然而,不要依賴這一機(jī)制,從調(diào)試和性能兩個原因來考慮,應(yīng)當(dāng)確保釋放應(yīng)該釋放的內(nèi)存。剩下的優(yōu)點(diǎn)是在多線程環(huán)境下性能的提高,調(diào)試模式下檢測內(nèi)存錯誤等。
???????還有一個重要的原因,你不需要檢查這些內(nèi)存分配函數(shù)的返回值是否為null。當(dāng)內(nèi)存分配失敗,它們會發(fā)出E_ERROR錯誤,從而決不會返回到擴(kuò)展。
?
從PHP函數(shù)中返回值
?
擴(kuò)展API包含豐富的用于從函數(shù)中返回值的宏。這些宏有兩種主要風(fēng)格:第一種是RETVAL_type()形式,它設(shè)置了返回值但C代碼繼續(xù)執(zhí)行。這通常使用在把控制交給腳本引擎前還希望做的一些清理工作的時候使用,然后再使用C的返回聲明?”return”?返回到PHP;后一個宏更加普遍,其形式是RETURN_type(),他設(shè)置了返回類型,同時返回控制到PHP。下表解釋了大多數(shù)存在的宏。
?
| 設(shè)置返回值并且結(jié)束函數(shù) | 設(shè)置返回值 | 宏返回類型和參數(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,引擎會調(diào)用estrdup()重復(fù)s,使用拷貝。如果dup為0,就使用s |
| RETURN_STRINGL(s,?l,?dup) | RETVAL_STRINGL(s,?l,?dup) | 長度為l的字符串值。與上一個宏一樣,但因?yàn)閟的長度被指定,所以速度更快。 |
| RETURN_TRUE | RETVAL_TRUE | 返回布爾值true。注意到這個宏沒有括號。 |
| RETURN_FALSE | RETVAL_FALSE | 返回布爾值false。注意到這個宏沒有括號。 |
| RETURN_RESOURCE(r) | RETVAL_RESOURCE(r) | 資源句柄。 |
? ?
總結(jié)
以上是生活随笔為你收集整理的php调用C代码的方法详解和zend_parse_parameters函数详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 十六字令三首(说一说十六字令三首的简介)
- 下一篇: 如何用C语言编写PHP扩展的详解