當 PHP 內建函數無法滿足效能或功能需求時,編寫 C 語言擴展是終極解決方案。本文以 PHP 5.6 為例,完整記錄從 ext_skel 生成骨架、定義函數原型、修改 config.m4、實作 PHP_FUNCTION、phpize 編譯、安裝 .so、修改 php.ini、測試驗證的全流程,實作一個字串與整數連接的簡單擴展函數 extstrcat()。
# 1. 取得 PHP 原始碼 (含 ext 目錄)
wget https://php.net/distributions/php-5.6.40.tar.gz
tar -xzf php-5.6.40.tar.gz
cd php-5.6.40/ext
# 檔案: ext/extstrcat.def
string extstrcat(string strarg, int intarg)
./ext_skel --extname=extstrcat --proto=extstrcat.def
# 產生 ext/extstrcat/ 目錄,包含 config.m4, extstrcat.c, php_extstrcat.h 等
編輯 ext/extstrcat/config.m4,移除以下行前的 dnl 註解:
PHP_ARG_ENABLE(extstrcat, whether to enable extstrcat support,
[ --enable-extstrcat Enable extstrcat support])
PHP_FUNCTION(extstrcat)
{
char *strarg = NULL;
int argc = ZEND_NUM_ARGS();
int strarg_len;
long intarg;
char intargstr[10];
int retstrlen = 0;
char *retstr = NULL;
// 解析參數: "s"=string, "l"=long (整數)
if (zend_parse_parameters(argc TSRMLS_CC, "sl", &strarg, &strarg_len, &intarg) == FAILURE) {
return;
}
// 整數轉字串
snprintf(intargstr, 9, "%d", intarg);
// 計算結果長度並分配記憶體
retstrlen = strarg_len + strlen(intargstr) + 1; // +1 for null terminator
retstr = (char *)malloc(sizeof(char) * retstrlen);
memset(retstr, '\0', retstrlen);
// 連接字串
strncat(retstr, strarg, strlen(strarg));
strncat(retstr, intargstr, strlen(intargstr));
// 回傳字串 (第二參數 1 表示需複製字串)
RETURN_STRING(retstr, 1);
// 釋放臨時記憶體 (RETURN_STRING 複製後需自行 free)
free(retstr);
}
| 宏/函數 | 用途 |
|---|---|
ZEND_NUM_ARGS() | 取得實際傳入參數個數 |
zend_parse_parameters() | 解析參數: "s"=字串, "l"=長整數, "d"=雙精度, "b"=布林, "a"=陣列, "o"=物件, "r"=資源 |
TSRMLS_CC | 線程安全巨集 (PHP 5.6 必需,PHP 7 已移除) |
RETURN_STRING(str, duplicate) | 回傳字串: duplicate=1 複製字串, 0 直接使用 (需確保生命週期) |
RETURN_LONG(), RETURN_DOUBLE(), RETURN_BOOL() | 回傳其他型別 |
php_error() | 發出錯誤/警告/通知 |
cd ext/extstrcat
# 1. 生成 configure 腳本
phpize
# 2. 配置 (啟用擴展)
./configure --enable-extstrcat
# 3. 編譯
make
# 成功後在 modules/ 產生 extstrcat.so
# 查詢擴展目錄
php -i | grep extension_dir
# 輸出範例: /usr/local/lib/php/extensions/no-debug-non-zts-20131226/
# 複製 .so 檔案
cp modules/extstrcat.so /usr/local/lib/php/extensions/no-debug-non-zts-20131226/
extension = extstrcat.so
重啟 PHP-FPM / Apache 生效。
<?php
// test_extstrcat.php
if (!extension_loaded('extstrcat')) {
dl('extstrcat.' . PHP_SHLIB_SUFFIX); // CLI 可動態載入
}
$ret = extstrcat('testarg', 1234);
echo $ret; // 輸出: testarg1234
?>
$ php test_extstrcat.php
testarg1234
$ php -m | grep extstrcat
extstrcat
$ php -i | grep extstrcat
extstrcat support => enabled
| 問題 | 原因 | 解決 |
|---|---|---|
| configure 找不到 phpize | 未安裝 php-dev / php-devel 套件 | apt-get install php5-dev / yum install php-devel |
| 編譯錯誤: TSRMLS_CC 未定義 | PHP 7+ 已移除 TSRMLS | PHP 5.6 保留,確認 phpize 版本正確 |
| RETURN_STRING 第二參數警告 | PHP 7 移除 duplicate 參數 | PHP 5.6 需保留 1 |
| dl() 無法載入 | php.ini disable_functions 含 dl | 移除 dl 限制或直接修改 php.ini 載入 |
| 中文亂碼 | 字串編碼不一致 | 確保原始碼 UTF-8,Zend 引擎內部 UTF-8 |
PHP 擴展開發標準流程:定義原型 → ext_skel 生成骨架 → 修改 config.m4 → 實作 PHP_FUNCTION → phpize → configure → make → 安裝 .so → 修改 php.ini → 測試。核心技術點:zend_parse_parameters 參數解析、RETURN_STRING 回傳值處理、記憶體管理 (malloc/free)、TSRMLS_CC 線程安全。掌握此流程即可將效能關鍵路徑下沉至 C 層,突破 PHP 使用者空間限制。