使用 C 编写基础 PHP 扩展
构建并加载您自己的原生扩展
使用 C 编写基础 PHP 扩展 是 CoddyKit 上的免费 PHP Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 PHP Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 PHP Academy 课程共包含 4 节课。
PHP 中的本机代码
当纯 PHP 的速度不够快,或者您需要绑定 C 库时,可以使用 C 针对 Zend API 编写PHP 扩展。该扩展会提供 PHP 可以直接调用的本机函数和类,不产生 VM 开销。
本课将端到端构建一个最小的 hello 扩展:包括骨架、函数、构建、加载和测试。
构建工具链
扩展使用 PHP 的 phpize 构建;它会利用您已安装的 PHP 头文件准备基于自动配置的构建。您需要 php-dev/php-devel(提供 phpize 和 php-config),以及 C 编译器和 make。
# Install build prerequisites (Debian/Ubuntu)
sudo apt install php-dev build-essential
# Confirm the tools exist
phpize --version
php-config --extension-dir # where the .so will be installedconfig.m4
每个扩展都需要一个 config.m4,用于注册构建标志并声明源文件。phpize 会读取它来生成配置脚本。
dnl config.m4 for the 'hello' extension
PHP_ARG_ENABLE([hello],
[whether to enable hello support],
[AS_HELP_STRING([--enable-hello], [Enable hello])],
[no])
if test "$PHP_HELLO" != "no"; then
PHP_NEW_EXTENSION(hello, hello.c, $ext_shared)
fi扩展头文件
C 源代码包含 Zend/PHP 头文件并声明模块入口。php.h 会引入核心 API;ext/standard/info.h 用于生成 phpinfo() 输出。每个扩展都会定义一个 zend_module_entry。
/* hello.c — includes */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "ext/standard/info.h"
#include "hello_arginfo.h" /* generated from stub */参数信息存根
现代 PHP 会从 .stub.php 文件生成参数元数据。您可以使用类似 PHP 的语法编写函数签名;gen_stub.php 会生成 hello_arginfo.h。这样可以确保反射和类型信息准确无误。
<?php
// hello.stub.php — describes the native function's signature
/** @generate-class-entries */
function hello_greet(string $name): string {}
?>实现函数
原生函数是一个带有 PHP_FUNCTION 标记的 C 函数。您可以使用 ZEND_PARSE_PARAMETERS 宏解析传入参数,并通过 RETURN_* 宏返回值。这里我们会构建一个问候字符串。
/* hello.c — the native function */
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();
/* Build "Hello, <name>!" into a new zend_string */
zend_string *result = strpprintf(0, "Hello, %s!", name);
RETURN_STR(result); /* hands ownership to the engine */
}模块入口
zend_module_entry 将所有部分串联起来:名称、版本、函数表(来自参数信息)以及生命周期钩子(MINIT、RINIT、MINFO)。ZEND_GET_MODULE 会导出加载器要查找的入口符号。
/* hello.c — module wiring */
zend_module_entry hello_module_entry = {
STANDARD_MODULE_HEADER,
"hello", /* extension name */
ext_functions, /* function table from arginfo */
NULL, /* MINIT (module startup) */
NULL, /* MSHUTDOWN */
NULL, /* RINIT (per-request) */
NULL, /* RSHUTDOWN */
PHP_MINFO(hello), /* phpinfo section */
"0.1.0",
STANDARD_MODULE_PROPERTIES
};
#ifdef COMPILE_DL_HELLO
ZEND_GET_MODULE(hello)
#endif内存:emalloc 与 malloc 的对比
在扩展内部,请使用 emalloc/efree 分配请求生命周期内的内存(由 Zend 内存管理器跟踪,并在请求结束时释放),而不要使用原始的 malloc。对于持久(跨请求)分配,请使用 pemalloc。通过 RETURN_STR 返回 zend_string 会将所有权转交给引擎,由引擎负责释放。
/* Request-scoped buffer the engine will clean up on error/shutdown */
char *buf = emalloc(64);
/* ... use buf ... */
efree(buf);
/* Persistent allocation surviving the request (rare) */
/* char *cfg = pemalloc(128, 1); ... pefree(cfg, 1); */构建扩展
经典的三步构建流程:使用 phpize 创建基础结构,使用带有启用标志的 ./configure 进行配置,然后运行 make。请记得先运行 gen_stub.php 生成参数信息头文件。
# Generate arginfo from the stub
php /path/to/php-src/build/gen_stub.php hello.stub.php
# Scaffold + configure + compile
phpize
./configure --enable-hello
make
# Result lands in modules/hello.so
ls -la modules/hello.so加载与测试
使用 -d extension=... 加载编译后的 .so(或者将其添加到 ini 文件中)。然后,像调用内置函数一样从 PHP 中调用这个原生函数。扩展安装完成后,您的测试/验证脚本就会是这个样子。
<?php
// After: php -d extension=./modules/hello.so test.php
if (!extension_loaded('hello')) {
fwrite(STDERR, "hello extension not loaded\n");
exit(1);
}
echo hello_greet('Zend') . PHP_EOL; // Hello, Zend!
var_dump(extension_loaded('hello')); // bool(true)
?>何时(不)应编写扩展
原生扩展会带来维护成本:C 内存错误、每个 PHP 次版本都需要重新构建,以及 ABI 破坏性变更。在采用原生实现之前,请考虑使用 FFI(无需编译扩展即可从 PHP 调用 C 库),或使用纯 PHP 优化。当您需要最高速度、深度引擎集成,或需要干净地封装复杂的 C 库时,再选择 C 扩展。
<?php
// FFI alternative: call a C library directly, no extension build
$ffi = FFI::cdef(
"int abs(int);", // declare the symbol
"libc.so.6"
);
echo $ffi->abs(-42) . PHP_EOL; // 42
?>快速检查
在扩展内部,应使用哪个内存分配器来存放请求生命周期内的内存?
回顾
您构建了一个最小的 C 扩展:config.m4 注册构建过程,.stub.php 生成参数信息,PHP_FUNCTION 使用 ZEND_PARSE_PARAMETERS 解析参数并通过 RETURN_STR 返回值来实现逻辑,而 zend_module_entry 则连接起生命周期钩子。使用 phpize → configure → make 进行构建,加载 .so,然后从 PHP 中进行测试。请求内存应使用 emalloc——在决定采用原生代码之前,也请考虑 FFI。
常见问题解答
「使用 C 编写基础 PHP 扩展」课时是免费的吗?
是的 — 「使用 C 编写基础 PHP 扩展」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 PHP Academy 课程的其余内容,请升级到 CoddyKit PRO。 PHP Academy 课程共包含 4 节课。
「使用 C 编写基础 PHP 扩展」这节课中我会学到什么?
构建并加载您自己的原生扩展 你通过在浏览器中直接运行的动手代码来练习 PHP Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 PHP Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 PHP Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「使用 C 编写基础 PHP 扩展」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 PHP Academy 课中编写并运行代码吗?
能。每节 PHP Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- Zend 引擎的工作原理
- 内存管理与垃圾回收
- OPcache 与 JIT 编译
- 使用 C 编写基础 PHP 扩展