開始看PHP內核也有一段時間了,現在開始邊學邊總結,今天就總結一下如何創建自己的PHP擴展。
我的環境如下:
系統:Ubuntu 14.04
php版本:5.5.19
參考摘錄:用C/C++擴展你的PHP
PHP取得成功的一個主要原因之一是它擁有大量的可用擴展。web開發者無論有何種需求,這種需求最有可能在PHP發行包裡找到。PHP發行包包括支持各種數據庫,圖形文件格式,壓縮,XML技術擴展在內的許多擴展。
擴展API的引入使PHP3取得了巨大的進展,擴展API機制使PHP開發社區很容易的開發出幾十種擴展。現在,兩個版本過去了,API仍然和PHP3時的非常相似。擴展主要的思想是:盡可能的從擴展編寫者那裡隱藏PHP的內部機制和腳本引擎本身,僅僅需要開發者熟悉API。
有兩個理由需要自己編寫PHP擴展。第一個理由是:PHP需要支持一項她還未支持的技術。這通常包括包裹一些現成的C函數庫,以便提供PHP接口。例如,如果一個叫FooBase的數據庫已推出市場,你需要建立一個PHP擴展幫助你從PHP裡調用FooBase的C函數庫。這個工作可能僅由一個人完成,然後被整個PHP社區共享(如果你願意的話)。第二個不是很普遍的理由是:你需要從性能或功能的原因考慮來編寫一些商業邏輯。
假設你正在開發一個網站,需要一個把字符串重復n次的函數。下面是用PHP寫的例子:
function util_str_repeat($string, $n){ $result = ""; for($i = 0; $i < $n; $i++){ $result .= $string; } return $result; } util_str_repeat("One", 3);// returns "OneOneOne". util_str_repeat("One", 1);// returns "One".
假設由於一些奇怪的原因,你需要時常調用這個函數,而且還要傳給函數很長的字符串和大值n。這意味著在腳本裡有相當巨大的字符串連接量和內存重新分配過程,以至顯著地降低腳本執行速度。如果有一個函數能夠更快地分配大量且足夠的內存來存放結果字符串,然後把$string重復n次,就不需要在每次循環迭代中分配內存。
為擴展建立函數的第一步是寫一個函數定義文件,該函數定義文件定義了擴展對外提供的函數原形。該例中,定義函數只有一行函數原形util_str_repeat() :
string util_str_repeat(string str, int n)
函數定義文件的一般格式是一個函數一行。你可以定義可選參數和使用大量的PHP類型,包括: bool, float, int, array等。
保存為util.def文件至PHP原代碼目錄樹下(即與ext_skel文件放在同一目錄下,我的目錄是/usr/share/php5/)。
然後就是通過擴展骨架(skeleton)構造器運行函數定義文件的時機了。該構造器腳本就是ext_skel。假設你把函數定義保存在一個叫做util.def的文件裡,而且你希望把擴展取名為util,運行下面的命令來建立擴展骨架:
sudo ./ext_skel --extname=util --proto=util.def
執行之後,我這裡報了如下錯誤:
./ext_skel: 1: cd: can't cd to /usr/lib/php5/skeleton Creating directory util awk: cannot open /create_stubs (No such file or directory) Creating basic files: config.m4 config.w32 .svnignore util.c./ext_skel: 216: ./ext_skel: cannot open /skeleton.c: No such file php_util.h./ext_skel: 234: ./ext_skel: cannot open /php_skeleton.h: No such file CREDITS./ext_skel: 238: ./ext_skel: cannot open /CREDITS: No such file EXPERIMENTAL./ext_skel: 242: ./ext_skel: cannot open /EXPERIMENTAL: No such file tests/001.phpt./ext_skel: 247: ./ext_skel: cannot open /tests/001.phpt: No such file util.php./ext_skel: 251: ./ext_skel: cannot open /skeleton.php: No such file rm: cannot remove ‘function_entries’: No such file or directory rm: cannot remove ‘function_declarations’: No such file or directory rm: cannot remove ‘function_stubs’: No such file or directory [done]. To use your new extension, you will have to execute the following steps: 1. $ cd .. 2. $ vi ext/util/config.m4 3. $ ./buildconf 4. $ ./configure --[with|enable]-util 5. $ make 6. $ ./php -f ext/util/util.php 7. $ vi ext/util/util.c 8. $ make Repeat steps 3-6 until you are satisfied with ext/util/config.m4 and step 6 confirms that your module is compiled into PHP. Then, start writing code and repeat the last two steps as often as necessary.
很明顯是/usr/lib/php5/skeleton路徑的錯誤,編輯ext_skel文件,將/usr/lib/php5/skeleton修改為/usr/share/php5/skeleton,然後移除掉生成的util文件夾,再次執行之前的命令,成功後提示如下:
Creating directory util Creating basic files: config.m4 config.w32 .svnignore util.c php_util.h CREDITS EXPERIMENTAL tests/001.phpt util.php [done]. To use your new extension, you will have to execute the following steps: 1. $ cd .. 2. $ vi ext/util/config.m4 3. $ ./buildconf 4. $ ./configure --[with|enable]-util 5. $ make 6. $ ./php -f ext/util/util.php 7. $ vi ext/util/util.c 8. $ make Repeat steps 3-6 until you are satisfied with ext/util/config.m4 and step 6 confirms that your module is compiled into PHP. Then, start writing code and repeat the last two steps as often as necessary.
然後采用靜態編譯的方式編譯擴展。為了使擴展能夠被編譯,需要修改擴展目錄util/下的config.m4文件。擴展沒有包裹任何外部的C庫,你需要添加支持–enable-util配置開關到PHP編譯系統裡(–with-extension 開關用於那些需要用戶指定相關C庫路徑的擴展)。找到如下內容:
dnl PHP_ARG_ENABLE(util, whether to enable util support, dnl Make sure that the comment is aligned: dnl [ --enable-util Enable util support])
將前面的dnl 去掉,修改為如下結果:
PHP_ARG_ENABLE(util, whether to enable util support, Make sure that the comment is aligned: [ --enable-util Enable util support])
然後修改util.c文件,找到如下代碼:
PHP_FUNCTION(util_str_repeat) { 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, "util_str_repeat: not yet implemented"); }
將其修改為如下代碼:
PHP_FUNCTION(util_str_repeat) { 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); }
裡面的具體內容,就不在這裡說了,之後會慢慢寫到。
然後就是編譯,安裝。在util目錄下,命令如下(命令可能都需要加sudo):
phpize ./configure make make test make install
然後配置生成的擴展文件,在php5.5版本中,進入到/etc/php5/mods-available目錄下,創建util.ini文件,寫入如下內容:
extension=util.so
然後enable util擴展
sudo php5enmod util
最後,重啟php-fpm
sudo service php5-fpm restart
創建一個php文件,測試一下,測試文件如下:
<?php for ($i = 1; $i <= 3; $i++) { print util_str_repeat("CraryPrimitiveMan ", $i); print "\n"; } ?>
執行結果如下:
CraryPrimitiveMan CraryPrimitiveMan CraryPrimitiveMan CraryPrimitiveMan CraryPrimitiveMan CraryPrimitiveMan
這樣我們就成功創建了一個包含簡單的PHP函數的擴展。
盜圖一張~~
今天就先到這裡~~