目標
創建一個名為 hello 的 PHP 擴展,並實現裡面唯一的函數 hello_world,作用是打印出 "Hello World" 字符串。
前提條件
一台已經安裝了 C 編譯器、PHP 運行環境的電腦,一個稱手的文本編輯器。
重要提示:不要試圖在 Windows 下寫 PHP 擴展,Visual C、MinGW 的編譯器都不好用,我曾經搗鼓了一個多星期也沒能在 Windows 下編譯成功過。所以至少要在一個 Unix 環境下進行。Mac 和各種 Linux 環境都可以。
下載 PHP 源代碼
先用 php -v 確定系統上的 PHP 版本,再到 php.net 上下載相應的源代碼包。解壓到某個目錄下,如 php5-5.3.5。源代碼目錄裡,ext 目錄下即是所有的 PHP 擴展所在的地方,其他的目錄暫時不必考慮。
生成 PHP 擴展的框架代碼
在 php5-5.3.5/ext 目錄下,有一個名為 ext_skel 的文件,這是用來創建擴展的一個簡便的工具。確保它有可執行權限(chmod u+x ext_skel),在終端下執行
./ext_skel --extname=hello
即會在 ext 目錄下創建一個 hello 的目錄,裡面是初始的骨架代碼。下一步的任務是創建 hello 擴展,並實現 hello_world 函數。
編輯 config.m4
用文本編輯器打開 ext/hello/config.m4,裡面有大量的注釋說明(以 dnl 開頭的行),基本上已經把很多問題說明白了。這裡要做的就是把
dnl PHP_ARG_ENABLE(hello, whether to enable hello support, dnl Make sure that the comment is aligned: dnl [ --enable-hello Enable hello support])
這三行取消注釋。這樣在接下來的編譯時,可以用 ./configure --enable-hello 來編譯我們剛剛寫的擴展。
重新生成 configure
回到源代碼根目錄,運行 ./buildconf --force,會激活 configure --enable-hello 參數。如果你在運行 buildconf 時報下面的錯誤:
buildconf: Your version of autoconf likely contains buggy cache code. Running vcsclean for you. To avoid this, install autoconf-2.13.
請安裝 autoconf-2.13(ubuntu 懶人的用法)
sudo apt-get install autoconf2.13
編譯擴展
此時的 hello 擴展已經可以編譯了,雖然還沒有實現其中的 hello_world 函數。先編譯一下,確保沒有環境配置上的問題。
./configure --enable-hello make
經過一段時間(其實是把整個 PHP 也編譯出來了),用
./sapi/cli/php -f ext/hello/hello.php
檢查一下擴展編譯情況。不出意外的話,應該提示
Functions available in the test extension: confirm_hello_compiled Congratulations! You have successfully modified ext/hello/config.m4. Module hello is now compiled into PHP.
編寫 hello_world 函數
聲明函數:打開 ext/hello/php_hello.h,在
PHP_MINIT_FUNCTION(hello); PHP_MSHUTDOWN_FUNCTION(hello); PHP_RINIT_FUNCTION(hello); PHP_RSHUTDOWN_FUNCTION(hello); PHP_MINFO_FUNCTION(hello);
後面添加
PHP_FUNCTION(hello_world);
即在擴展的頭文件中聲明了 hello_world 的函數的原型。PHP_FUNCTION 是用來定義 PHP 函數的 C 語言宏。至於宏展開後的樣子,幾乎不用去想。只管用就可以了。
實現函數:打開 hello.c,在文件的末尾添加
PHP_FUNCTION(hello_world){ php_printf("Hello World");return;}
這裡即是 hello_world 函數的實現。php_printf 的作用是向 SAPI 輸出一段字符串,類似於 PHP 語言中的 echo。
接下來還需要將 hello_world 函數注冊到 zend_module_entry,這樣這個函數才能在 PHP 程序中變成“可見”的。找到
const zend_function_entry hello_functions[]={ PHP_FE(confirm_hello_compiled, NULL)/* For testing, remove later. */{NULL, NULL, NULL}/* Must be the last line in hello_functions[] */};
將其修改為:
const zend_function_entry hello_functions[]={ PHP_FE(confirm_hello_compiled, NULL)/* For testing, remove later. */ PHP_FE(hello_world, NULL){NULL, NULL, NULL}/* Must be the last line in hello_functions[] */};
此時整個的 hello 擴展的代碼就編寫完了。最後再來 make 一下。
測試
在終端下運行 sapi/cli/php -r 'hello_world();echo "\n";',如果看到輸出“Hello World”,就成功了。
如何把擴展編譯成 .so 文件
上面編譯的結果是把 hello 擴展編譯進了 PHP 核心中。如果想要編譯成 .so 擴展,以便發布出去的話。需要使用
./configure --enable-hello=shared make
這樣編譯完成後,會在 modules 目錄下生成 hello.so 文件。把它復制到你的 PHP 運行環境的 extension_dir 下就可以像其他擴展一樣使用了。需要注意的是 PHP 版本。如果你是在 PHP 5.3.5 的源代碼環境中編譯的擴展,則生成的 .so 文件只能用在 PHP 5.3.5 的運行環境中。
最後提一下,如果對 PHP 擴展有興趣,可以看看《Extending and Embedding PHP》這本書,作者還是個 MM。目前沒有中文版,英文電子版的自己搜。
摘自 lostwolf blog