PHP內核的學習--創建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函數的擴展。