看完前言中所說的一些內容后,各位應該對PHP擴展開發有個籠統的了解了,可能有些人會覺得開發擴展很麻煩很復雜,實際上並非如此,這一篇我們就快速進入角色,開發出我們的第一個擴展。
一、編譯PHP
開發之前還需要先准備好PHP源碼並編譯,過程如下:
tar -zxvf php-5.3.9.tar.gz cd php-5.3.9
我使用的是php5.3.9,解壓后,我們進入了PHP源碼目錄,然后我們直接編譯並增加php.ini:
./configure --prefix=/usr/local/webserver/php --enable-fastcgi --enable-fpm --enable-debug make && make install cp /home/soft/php-5.3.9/php.ini-development /usr/local/webserver/php/lib/php.ini
編譯完成,我沒有靜態編譯其他擴展,但是開啟了debug,這個后面會用到。然后修改php.ini中對應的項,這里就不細說了。
現在把PHP相關加入環境變量中,省去后面很多工作:
vim /root/.bash_profile
我是使用root,其他不同用戶修改對應用戶目錄下的.bask_profile文件,在文件中的PATH后面加入:/usr/local/webserver/php/bin/,類似下面這樣:
PATH=$PATH:$HOME/bin:/usr/local/webserver/php/bin/
環境變量設置好了,我們查看下PHP版本:

OK,編譯工作完成,讓我們繼續。
二、典型開發流程
一個典型的擴展開發流程如下圖:

三、擴展功能定義
先定義我們要完成的擴展功能:
該擴展只有一個功能,就是重寫PHP的系統函數ip2long(),解決ip2long在32位與64位系統下值不同的問題(該問題是因為32位與64位的整形范圍不同導致的,具體原因請google)。
我們新的ip2long固定返回32位有符號整數,范圍-2147483648 到 2147483647,與32位系統相同。
我們的擴展名稱為 myip,函數名為 ip2long32
擴展的功能與名稱都OK了,現在按流程進行開發。
四、正式開發
1. 生成開發骨架
首先進入源碼擴展目錄:
cd /home/soft/php-5.3.9/ext
然后了解下PHP提供的擴展骨架工具ext_skel生成骨架,ext_skel的用法如下:
./ext_skel --extname=module [--proto=file] [--stubs=file] [--xml[=file]] [--skel=dir] [--full-xml] [--no-help] --extname=module module is the name of your extension(模塊名,會在當前目錄創建一個該名稱子目錄) --proto=file file contains prototypes of functions to create(函數原型定義文件) --stubs=file generate only function stubs in file --xml generate xml documentation to be added to phpdoc-cvs --skel=dir path to the skeleton directory(設置骨架生成的目錄,不設置該項則默認在ext/extname下) --full-xml generate xml documentation for a self-contained extension (not yet implemented) --no-help don't try to be nice and create comments in the code and helper functions to test if the module compiled (生成的代碼中不顯示各種幫助注釋)
這次我們准備用到兩個選項,--extname=myip 即定義擴展的名稱,而--proto=myip.pro則是定義擴展的函數原型,首先我們生成擴展函數原型文件:
vim myip.pro
加入以下內容:
int ip2long32(string ip)
這意味着我們的擴展中有一個函數,返回值為int型,輸入為string。
這時候執行以下命令生成擴展骨架:
./ext_skel --extname=myip --proto=myip.pro
OK,這時候你會發現在當前PHP擴展目錄下生成了一個子目錄myip,進入myip看下:
cd myip
ll
你會發現生成了一堆文件,如下圖:

此時我們就可以進行第二步了。
2. 修改config.m4
關於config.m4文件的功能,我們留到后面的文章中在詳細進行說明,現在只說明要做什么。
使用vim編輯config.m4:
vim config.m4
將16至18行行首的dnl去掉,如下:

具體這樣做的原因在后面的文章中會說明,這邊我們直接退出並保存config.m4,繼續進入下一步。
3. 編碼
重頭戲來啦,終於可以進入myip.c中進行功能的編碼了,一起歡呼下吧!
vim myip.c
找到下圖所示的位置:

圖中就是擴展骨架工具根據我們提供的函數原型生成的對應函數,此處有幾個需要注意的地方:
1. PHP_FUNCTION:是PHP核心定義的一個宏,與ZEND_FUNCTION相同,用於定義擴展函數,實際生成的函數名稱為zif_ip2long32。
2. zend_parse_parameters:由於PHP為弱類型語言,而C是強類型,因此需要使用該函數用於接收PHP傳入的參數,並進行一定的類型轉換,將PHP的變量轉為C語言能夠辨認的類型。
zend_parse_parameters函數的原型如下:
zend_parse_parameters(int num_args TSRMLS_CC, char *type_spec, …);
參數說明:
-
- num_args:傳遞給函數的參數個數。通常的做法是使用宏 ZEND_NUM_ARGS()。
- TSRMLS_CC:線程安全,總是傳遞TSRMLS_CC宏。 詳解:http://www.54chen.com/php-tech/what-is-tsrmls_cc.html
- type_spec:第三個參數是一個字符串,指定了函數期望的參數類型
- ...:需要隨參數值更新的變量列表]
type_spec是格式化字符串,其常見的含義如下:參數 代表着的類型b Booleanl Integer 整型d Floating point 浮點型s String 字符串r Resource 資源a Array 數組o Object instance 對象O Object instance of a specified type 特定類型的對象z Non-specific zval 任意類型~Z zval**類型f 表示函數、方法名稱
我們將該函數修改為如下內容:
PHP_FUNCTION(ip2long32) { char *ip = NULL; int argc = ZEND_NUM_ARGS(); int ip_len; if (zend_parse_parameters(argc TSRMLS_CC, "s", &ip, &ip_len) == FAILURE) { return; } int32_t ip_int32; unsigned char ip1, ip2, ip3, ip4; sscanf(ip, "%hhu.%hhu.%hhu.%hhu", &ip1, &ip2, &ip3, &ip4); ip_int32 = (int32_t)((ip1 << 24) | (ip2 << 16) | (ip3 << 8) | ip4); RETURN_LONG(ip_int32); }
功能完成了,這邊有個RETURN_LONG(ip_int32)比較特殊,這也是PHP內核提供的宏,用於返回值給PHP,具體說明如下:
設置返回值並且結束函數 設置返回值 宏返回類型和參數
RETURN_LONG(l) RETVAL_LONG(l) 整數
RETURN_BOOL(b) RETVAL_BOOL(b) 布爾數(1或0)
RETURN_NULL() RETVAL_NULL() NULL
RETURN_DOUBLE(d) RETVAL_DOUBLE(d) 浮點數
RETURN_STRING(s, dup) RETVAL_STRING(s, dup) 字符串。如果dup為1,引擎會調用estrdup()重復s,使用拷貝。如果dup為0,就使用s
RETURN_STRINGL(s, l, dup) RETVAL_STRINGL(s, l, dup) 長度為l的字符串值。與上一個宏一樣,但因為s的長度被指定,所以速度更快。
RETURN_TRUE RETVAL_TRUE 返回布爾值true。注意到這個宏沒有括號。
RETURN_FALSE RETVAL_FALSE 返回布爾值false。注意到這個宏沒有括號。
RETURN_RESOURCE(r) RETVAL_RESOURCE(r) 資源句柄。
編碼完成了,保存並退出,然后我們可以開始編譯了。
4. 編譯
phpize ./configure --with-php-config=/usr/local/webserver/php/bin/php-config make && make install
不出意外的話編譯完成后會有如下提示:
Installing shared extensions: /usr/local/webserver/php/lib/php/extensions/debug-non-zts-20090626/
進入該目錄看下是否已經有myip.so,有的話最后我們就可以修改php.ini載入該so文件
5. 修改php.ini
cd /usr/local/webserver/php/lib
vim php.ini
修改extension_dir,並加入 extension = myip.so
extension_dir = "/usr/local/webserver/php/lib/php/extensions/debug-non-zts-20090626/"
extension = myip.so
退出保存,並重啟php,如果是使用Phpfpm的話可以執行如下命令:
kill -USR2 `cat /usr/local/webserver/php/var/run/php-fpm.pid`
看下擴展是否正常載入:
[root@tm977 lib]# php -m|grep myip myip
說明已經正常載入了,最后我們測試下擴展函數吧!
6. 測試
php -r "var_dump(ip2long32('192.168.1.1'));" int(-1062731519) php -r "var_dump(ip2long('192.168.1.1'));" int(3232235777)
如上所示,ip2long32輸出的是32位有符號整數,而ip2long輸出的是64位無符號整數,大功告成!
五、小結
通過這一次的開發示例,是不是覺得其實開發一個擴展很簡單?
確實是的,但是也別高興的太早了,實際上要開發出功能強大的擴展遠不是這么簡單的事情,后面的文章我會繼續深入,一邊開發一邊了解更加復雜的PHP核心代碼。
