php 扩展中不存在直接供 rust ffi 调用的 piso 函数;需先验证其 c 符号是否真实导出,再确认内存管理与 zend 依赖——否则必然崩溃或失败。

PHP 的 piso 函数并不存在——这是关键前提。你实际想调用的,很可能是 PHP 扩展中某个叫 piso_* 开头的 C 函数(比如 piso_encode、piso_decode),或者误记了函数名(如把 base64_encode 或某个私有扩展函数记成 piso)。Rust 无法直接调用 PHP 函数,只能通过 FFI 调用其底层导出的 C 符号。
确认目标函数是否真实存在于 PHP 扩展的动态库中
先别写 Rust 代码,得验证 C 接口是否存在且可导出:
- 找到对应 PHP 扩展的 .so 文件(通常在
/usr/lib/php/*/piso.so或ext/piso/modules/piso.so) - 用
nm -D piso.so | grep piso或objdump -T piso.so | grep piso查看是否有非静态、带版本符号的导出函数(如T piso_encode) - 若输出为空或只有
U(undefined)或W(weak),说明该函数未被导出,不能直接 FFI 调用 - 检查扩展源码中的
PHP_FUNCTION(piso_encode)是否被ZEND_BEGIN_ARG_INFO_EX和PHP_FE注册——这仅影响 PHP 层调用,和 C 导出无关;真正需要的是在 C 源码里加__attribute__((visibility("default")))并确保链接时未 strip 符号
Rust 中声明和调用导出的 C 函数(以 piso_encode 为例)
假设已确认 libpiso.so 导出了 piso_encode,且签名为 int piso_encode(const char*, int, char**, int*):
#[link(name = "piso", kind = "dylib")]
extern "C" {
fn piso_encode(
input: *const std::ffi::c_char,
input_len: std::ffi::c_int,
output: *mut *mut std::ffi::c_char,
output_len: *mut std::ffi::c_int,
) -> std::ffi::c_int;
}
fn call_piso_encode(input: &str) -> Result<Vec<u8>, String> {
let c_input = std::ffi::CString::new(input).map_err(|e| e.to_string())?;
let mut c_output: *mut std::ffi::c_char = std::ptr::null_mut();
let mut c_output_len: std::ffi::c_int = 0;
let ret = unsafe {
piso_encode(
c_input.as_ptr(),
input.len() as std::ffi::c_int,
&mut c_output,
&mut c_output_len,
)
};
if ret != 0 {
return Err(format!("piso_encode failed with code {}", ret));
}
// 注意:需确认 piso_encode 是否 malloc 了 output,以及谁负责 free
let slice = unsafe { std::slice::from_raw_parts(c_output as *const u8, c_output_len as usize) };
let result = slice.to_vec();
// 若 piso_encode 分配了内存,通常需配套提供 piso_free 或类似函数来释放
// 否则这里要调用 libc::free(c_output as *mut libc::c_void)
Ok(result)
}
⚠️ 关键点:必须查清内存所有权。很多 PHP 扩展函数返回的指针指向内部缓冲区(不可 free),或要求调用方用特定函数释放——否则必然 crash 或内存泄漏。
立即学习“PHP免费学习笔记(深入)”;
常见失败原因与绕过建议
即使符号存在,Rust 调用仍大概率失败,原因往往不在 Rust 侧:
- PHP 扩展依赖 Zend API 符号(如
zend_error、emalloc),而 Rust 进程没初始化 Zend VM,直接调用会 segfault —— 此时不能绕过,必须启动嵌入式 PHP 解释器(用libphp)或改用进程间通信(如 PHP CLI + stdin/stdout) -
LD_LIBRARY_PATH未包含 PHP 扩展路径,导致dlopen失败;可在 Rust 中用std::env::set_var("LD_LIBRARY_PATH", "...")预设,但更可靠的是用patchelf --set-rpath重写 so 的 rpath - 函数签名不匹配(例如把
size_t当作c_uint,或忽略 const 修饰符),会导致栈错位或静默错误;务必对照扩展的头文件(如piso.h)定义 - PHP 扩展用了线程局部存储(TLS)或全局状态(如
EG(current_execute_data)),在 Rust 线程中调用会崩溃 —— 这类函数本质不可安全 FFI
最常被忽略的一点:PHP 扩展不是为独立 C 调用设计的。哪怕符号导出、签名正确、内存处理得当,只要它内部调用了任何 Zend 内部函数,就几乎必然失败。这时候“操作”本身是徒劳的,得换思路——要么找纯 C 实现的替代库,要么让 PHP 进程做计算,Rust 做调度。











