照着走一遍,你就能从零编译出一个能被 `php -m` 认出来的 C 语言扩展,并知道每个文件、每条命令到底在干什么。
第一步:装齐工具链
扩展开发需要编译器 + PHP 的开发头文件 + 构建脚本工具:
apt install php-dev gcc make autoconf
# CentOS / Rocky(以 PHP 8.2 为例)
yum install php-devel gcc make autoconf
装完先确认两件事:
php -v # 运行时版本,比如 8.2.15
php-config --version # 开发包版本,必须和上面一致
php-config --extension-dir # .so 最终要落到的目录
注意:`phpize` 的版本和实际运行 PHP 的版本必须严格一致。版本对不上,编译能过、加载必挂。宝塔面板装的 PHP,用 `/www/server/php/82/bin/phpize` 这种绝对路径,别用系统的。
第二步:生成扩展骨架
PHP 自带一个生成器 `ext_skel.php`,它躺在 PHP 源码包的 `ext/` 目录里。
- 如果有源码包:`cd php-src/ext && php ext_skel.php --ext=hello`
- 发行版的 `php-dev` 包常常不带这个文件,那就去 php.net 下对应版本的源码包,解压后只取 `ext/ext_skel.php` 用,不需要编译整个 PHP。
执行后会得到 `hello/` 目录,里面最该关注三个文件:
- `config.m4` —— 告诉构建系统怎么编译
- `php_hello.h` —— 头文件,声明函数
- `hello.c` —— 真正的实现
注意:骨架只是省事,不是必需。手写这三个文件同样能编译,理解结构比会用生成器重要。
第三步:看懂 config.m4
打开 `config.m4`,核心是这段:
PHP_ARG_ENABLE([hello],
[whether to enable hello support],
[AS_HELP_STRING([--enable-hello], [Enable hello support])],
[no])
if test "$PHP_HELLO" != "no"; then
PHP_NEW_EXTENSION(hello, hello.c, $ext_shared)
fi
意思很直白:`./configure` 时传 `--enable-hello` 才编译,源码文件是 `hello.c`。如果以后拆成多个 `.c` 文件,在这里用空格隔开列全即可。
第四步:在 hello.c 里写函数
把 `hello.c` 里自带的一堆示例函数删掉,只留模块入口,然后加入自己的函数:
ZEND_BEGIN_ARG_INFO_EX(arginfo_hello_greet, 0, 0, 1)
ZEND_ARG_INFO(0, name)
ZEND_END_ARG_INFO()
PHP_FUNCTION(hello_greet)
{
char *name;
size_t name_len;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_STRING(name, name_len)
ZEND_PARSE_PARAMETERS_END();
RETURN_STR(strpprintf(0, "Hello, %s!", name));
}
static const zend_function_entry hello_functions[] = {
PHP_FE(hello_greet, arginfo_hello_greet)
PHP_FE_END
};
模块入口结构体里,`hello_functions` 就是函数注册表,PHP 侧能调到哪些函数全看它。
注意:C 扩展里不要用 `malloc` / `free`,要用 `emalloc` / `efree`;字符串用 `zend_string`。用错分配器会导致请求结束时内存统计错乱甚至崩溃。另外,绝不能返回函数内局部数组的指针,必须用 `RETURN_STR` 这类宏让引擎接管生命周期。
第五步:编译安装
cd hello
phpize
./configure --with-php-config=$(which php-config)
make
sudo make install
`make install` 会把 `hello.so` 拷到 `php-config --extension-dir` 显示的目录。
注意:非 root 环境或宝塔环境可能拷不进去,那就手动把 `modules/hello.so` 复制过去。改了 C 代码后必须重新 `make && make install`,重启 PHP 才会生效。
第六步:启用并验证
在 `php.ini` 末尾加一行:
extension=hello.so
重启 PHP(`systemctl restart php-fpm` 或面板里重载),然后验证:
php -m | grep hello
php -r 'echo hello_greet("world"), PHP_EOL;'
看到 `hello` 出现在模块列表、命令行输出 `Hello, world!`,第一个扩展就跑通了。
注意:如果 `php -m` 里没有,先看 `php -i | grep extension_dir` 确认路径,再用 `php -d extension=hello.so -v` 单独加载,报错信息会直接告诉你符号缺失还是版本不符。
小结
- 工具链核心是 `php-devel` + `phpize`,版本必须和运行环境一致
- `config.m4` 决定编译方式,`zend_function_entry` 决定对外暴露哪些函数
- 参数解析用 `Z_PARAM_*` 宏,返回字符串用 `RETURN_STR`,内存一律走 `emalloc` 系
- 流程固定:`phpize → configure → make → make install → 改 php.ini → 重启`
- 每改一次代码都要重编译并重启,扩展改动不会热生效