本文使用 Solder 库来实现PHP扩展程序,它是基于 php-rs 库的试验性项目,实现了php扩展的函数处理。 满足了字符串和数字类型基本要求。

soder 源库地址为 github , 这里使用的是 fork 的库, github.com/erasin/solder

2020-09-17 : 项目 XX/php-rust 中提供了更多的类型处理。简化函数注册流程。

创建项目

使用 cargo 来创建项目,然后创建编译配置文件.cargo/config

cargo new --lib rs-tool 
cd rs-tool
mkdir .cargo
touch config

编辑 .cargo/config,遵循toml格式。

[build]
rustflags = ["-C", "link-args=-Wl,-undefined,dynamic_lookup"]

引入库

编辑 Cargo.toml,追加引用

[dependencies]
solder = {git = "https://github.com/erasin/solder"}
lazy_static = "1.4"
md5 = "0.7.0"

[lib]
crate-type = ["dylib"]
name = "rstool"
  • lib 该节点定义输出类型.
    • name 定义输出文件名称,实际名称头部追加 lib,比如当前项目为 librstool.dylib.
    • dylib 类型在 osx 下为 .dylib, linux下为 .so 文件.

这里 github 速度太慢的化可以用 https://gitee.com/era/solder 或者自己镜像一个即可。

  • 引入 lazy_static 来制作计数器.
  • md5 来创建 rs_md5 函数.

创建扩展函数

编辑项目文件 src/lig.rs.

use solder::info::*;
use solder::zend::*;
use solder::*;

// 扩展简介
#[no_mangle]
pub extern "C" fn php_module_info() {
    print_table_start();
    print_table_row("tool written by dyuit", "enabled");
    print_table_end();
}


/// 函数封装
#[no_mangle]
pub extern "C" fn get_module() -> *mut Module {

    // 封装函数
    let fn_md5 = FunctionBuilder::new(c_str!("rs_md5"), rs_md5)
        .with_arg(ArgInfo::new(c_str!("str"), 0, 0, 0))
        .build();

    // 将函数追加到扩展中
    ModuleBuilder::new(c_str!("dyuit_tool"), c_str!("0.1.0-dev"))
        .with_info_function(php_module_info)
        .with_function(fn_md5) 
        .build()
        .into_raw()
}

// 引入md5
use md5;

// rs md5 函数
#[no_mangle]
pub extern "C" fn rs_md5(_data: &ExecuteData, retval: &mut Zval) {
    // 参数处理,如果多个参数就创建多个 zval 实例。
    let mut param_zval = Zval::new_as_null();
    // 解析参数,这里可以解析多个参数,不可以超过5个
    php_parse_parameters!(&mut param_zval);
    // 将参数转换为rust类型
    let s = String::try_from(param_zval).ok().unwrap();

    // md5 处理
    let digest = md5::compute(s);
    let re = format!("{:x}", digest);

    // 将数据封包返回,查看 solder 源码,检查支持的类型,这里多数为string和&str。
    php_return!(retval, re);
}

文件中

  • get_module 用来输出扩展
  • php_module_info 定义扩展注释
  • FunctionBuilder 来封装函数,多个参数用 with_arg 来定义参数,参数最大数量支持5个。
  • ModuleBuilder 中 with_function 来关联函数。

编译和测试

回到项目目录

cargo build 
# 加载扩展饼进入php命令
php -d extension=target/debug/librstool.dylib -a
# php 验证
php > echo rs_md5('123456');
e10adc3949ba59abbe56e057f20f883e
php > echo md5('123456');
e10adc3949ba59abbe56e057f20f883e
php > print_r(get_extension_funcs('dyuit_tool'));
Array
(
    [0] => rs_md5
)

如果成功后就可以生产 release 版本 ,加载到 php.ini 中就可以使用了。

libc 在交叉编译中会报错,生产对应平台文件,需要多个环境编译。

再创建函数 计数器加减

创建函数

use lazy_static::lazy_static;
use std::sync::Mutex;

lazy_static! {
    static ref NUM: Mutex<u32> = Mutex::new(0);
}

#[no_mangle]
pub extern "C" fn rs_add(_data: &ExecuteData, retval: &mut Zval) {
    let mut n = NUM.lock().unwrap();
    *n += 1;
    php_return!(retval, *n);
}

#[no_mangle]
pub extern "C" fn rs_less(_data: &ExecuteData, retval: &mut Zval) {
    let mut n = NUM.lock().unwrap();
    *n -= 1;
    php_return!(retval, *n);
}

加载函数到 get_module()

let fn_add = FunctionBuilder::new(c_str!("rs_add"), rs_lock).build();
let fn_less = FunctionBuilder::new(c_str!("rs_less"), rs_unlock).build();

ModuleBuilder::new(c_str!("dyuit_tool"), c_str!("0.1.0-dev"))
    .with_info_function(php_module_info)
    .with_function(fn_md5)
    .with_function(fn_add)
    .with_function(fn_less)
    .build()
    .into_raw()

结束语

PHP 7.4 的 FFI 改进 , 或许比扩展更好,有兴趣的同学可以去对比下。