php ffi 不能直接调用任意 .so/.dll,仅支持符合 c abi 的纯函数接口,不支持 c++ 特性;结构体易因内存对齐错位崩溃;errno 需手动读取;库加载受 dlopen、依赖和版本冲突三重限制。

PHP FFI 能不能直接调用任意 .so/.dll?
不能,FFI 只能调用符合 C ABI 的纯函数接口,不支持 C++ 类、重载、异常、STL 容器或带隐藏参数的函数(比如 this 指针)。你看到的 libcurl.so 或 libc.so.6 能调,是因为它们暴露的是 C 风格符号;但 libstdc++.so 里一堆 _ZStlsI... 这种名字的函数,FFI 解析不了。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 先用
nm -D /path/to/lib.so | grep your_func确认符号存在且无 C++ name mangling - 用
readelf -Ws /path/to/lib.so | grep FUNC检查函数是否为 GLOBAL + DEFAULT 绑定 - 优先选系统级 C 库(如
libc、libm)或明确提供 C API 的第三方库(如libsodium),避开封装层厚的 SDK
ffi\_cdef() 里写结构体时最常崩在哪?
崩在内存对齐和字段顺序——PHP FFI 不自动处理 #pragma pack 或编译器默认对齐,而 C 头文件里往往有隐式对齐要求。比如你照抄一个含 uint64_t 和 char[3] 的结构体,PHP 默认按 8 字节对齐,但目标库可能按 1 或 4 字节对齐,导致字段偏移错位、读出垃圾值甚至段错误。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 不要手写结构体定义,用
gcc -E header.h | grep -A20 "struct my_s {"预处理后看真实展开 - 在
ffi_cdef()中显式加__attribute__((packed))声明(如果 C 端也这么定义),例如:struct __attribute__((packed)) my_s { uint64_t a; char b[3]; }; - 用
sizeof($struct)和FFI::addr($struct)->a对比 C 端offsetof()值,验证字段偏移是否一致
调用 libc 函数为什么返回 -1 但 errno 没变?
因为 PHP FFI 默认不自动同步 errno。C 函数失败时设了 errno,但 PHP 层没主动读,下次调用前它可能已被其他系统调用覆盖。你看到 fflush() 返回 -1 却查不到具体原因,大概率是这个。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 调用后立刻用
FFI::cdef("int errno;", "libc.so.6")->errno读取(注意:不同平台libc名称可能为libc.so.6或libc.dylib) - 把 errno 读取封装进工具函数,别等报错后再想——比如
function get_errno() { return FFI::cdef("int errno;", "libc.so.6")->errno; } - 某些函数(如
open())失败才改errno,成功时它可能是上一次的残留值,务必结合返回值判断
FFI::load() 加载动态库失败的三个硬性条件
不是路径错、不是权限问题,而是三个底层限制卡死:库必须可被 dlopen() 打开;PHP 进程必须有执行该库所需的所有依赖(包括间接依赖);且不能与当前 PHP 进程链接的同名库版本冲突(比如你 load libssl.so.1.1,但 PHP 自己用的是 libssl.so.3,dlopen 会静默失败)。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 用
ldd /path/to/your.so检查所有依赖是否可解析,尤其注意not found行 - 用
strace -e trace=openat,open,openat64 php -r "FFI::load('/x.so');"看实际尝试打开哪些路径 - 避免在 Web SAPI(如 Apache mod_php)中 load 系统关键库,fork 后的子进程可能因库状态不一致崩溃;CLI 下更可控
FFI 最难的不是语法,是让 PHP 进程的符号空间和 C 库的运行时环境真正对齐——这中间没有魔法,只有 ldd、strace 和反复验证。











