Home > Article > Backend Development > How to use Rust extension in PHP program, php program rust extension_PHP tutorial
Rust in C or PHP
My basic starting point is to write some compilable Rust code into a library, write some C header files for it, and make an extension in C for the called PHP. It's not easy, but it's fun.
Rust FFI (foreign function interface)
The first thing I did was play around with Rust’s external function interface connecting Rust to C. I once wrote a flexible library using a simple method (hello_from_rust) with a single declaration (a pointer to a C char, otherwise known as a string). Here is the output of "Hello from Rust" after input.
// hello_from_rust.rs #![crate_type = "staticlib"] #![feature(libc)] extern crate libc; use std::ffi::CStr; #[no_mangle] pub extern "C" fn hello_from_rust(name: *const libc::c_char) { let buf_name = unsafe { CStr::from_ptr(name).to_bytes() }; let str_name = String::from_utf8(buf_name.to_vec()).unwrap(); let c_name = format!("Hello from Rust, {}", str_name); println!("{}", c_name); }
I split it from a Rust library called from C (or other!). Here's a good explanation of what comes next.
Compiling it will get a file of .a, libhello_from_rust.a. This is a static library that contains all its own dependencies, and we link it when compiling a C program, which allows us to do subsequent things. Note: After we compile, we will get the following output:
note: link against the following native artifacts when linking against this static library note: the order and any duplication can be significant on some platforms, and so may need to be preserved note: library: Systemnote: library: pthread note: library: c note: library: m
This is what the Rust compiler tells us to link against when we don’t use this dependency.
Call Rust from C
Now that we have a library, we have to do two things to make it callable from C. First, we need to create a C header file for it, hello_from_rust.h. Then link to it when we compile.
The following is the header file:
// hello_from_rust.h #ifndef __HELLO #define __HELLO void hello_from_rust(const char *name); #endif
This is a fairly basic header file, just providing a signature/definition for a simple function. Next we need to write a C program and use it.
// hello.c #include <stdio.h> #include <stdlib.h> #include "hello_from_rust.h" int main(int argc, char *argv[]) { hello_from_rust("Jared!"); }
We compile it by running this code:
gcc -Wall -o hello_c hello.c -L /Users/jmcfarland/code/rust/php-hello-rust -lhello_from_rust -lSystem -lpthread -lc -lm
Note that the -lSystem -lpthread -lc -lm at the end tells gcc not to link against those "native antiques" so that the Rust compiler can provide them when compiling our Rust library.
By running the following code we can get a binary file:
$ ./hello_c Hello from Rust, Jared!
Beautiful! We just called the Rust library from C. Now we need to understand how a Rust library gets into a PHP extension.
Call c from php
This part took me some time to figure out, the documentation is not the best in the world for php extensions. The best part is that the php source comes from bundling a script ext_skel (mostly stands for "Extended Skeleton") which generates most of the boilerplate code you need. You can get started by downloading the unquoted php source, writing the code into the php directory and running:
$ cd ext/ $ ./ext_skel --extname=hello_from_rust
This will generate the basic skeleton needed to create a php extension. Now, move the folders everywhere you want to keep your extensions locally. And move your
Enter the same directory. So now you should look at a directory like this:
. ├── CREDITS ├── EXPERIMENTAL ├── config.m4 ├── config.w32 ├── hello_from_rust.c ├── hello_from_rust.h ├── hello_from_rust.php ├── hello_from_rust.rs ├── libhello_from_rust.a ├── php_hello_from_rust.h └── tests └── 001.phpt
One directory, 11 files
You can see a good description of these files in the php docs above. Create an extended file. We'll get started by editing config.m4.
Without explanation, here are my results:
PHP_ARG_WITH(hello_from_rust, for hello_from_rust support, [ --with-hello_from_rust Include hello_from_rust support]) if test "$PHP_HELLO_FROM_RUST" != "no"; then PHP_SUBST(HELLO_FROM_RUST_SHARED_LIBADD) PHP_ADD_LIBRARY_WITH_PATH(hello_from_rust, ., HELLO_FROM_RUST_SHARED_LIBADD) PHP_NEW_EXTENSION(hello_from_rust, hello_from_rust.c, $ext_shared) fi
As I understand it, these are basic macro commands. But the documentation on these macros is quite poor (for example: google "PHP_ADD_LIBRARY_WITH_PATH" does not show results written by the PHP team). I came across this PHP_ADD_LIBRARY_PATH macro in a previous thread where someone was talking about linking a static library in a PHP extension. The other recommended macros in the comments were generated after I ran ext_skel.
Now that we have the configuration setup, we need to actually call the library from the PHP script. To do this we have to modify the automatically generated file, hello_from_rust.c. First we add the hello_from_rust.h header file to the include command. Then we need to modify the definition method of confirm_hello_from_rust_compiled.
#include "hello_from_rust.h" // a bunch of comments and code removed... PHP_FUNCTION(confirm_hello_from_rust_compiled) { char *arg = NULL; int arg_len, len; char *strg; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) { return; } hello_from_rust("Jared (from PHP!!)!"); len = spprintf(&strg, 0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "hello_from_rust", arg); RETURN_STRINGL(strg, len, 0); }
Note: I added hello_from_rust("Jared (fromPHP!!)!");.
Now, we can try to build our extension:
$ phpize $ ./configure $ sudo make install
That’s it, generate our meta configuration, run the generated configuration command, and install the extension. When installing, I had to use sudo myself because my user did not own the php extension for the installation directory.
Now, we can run it!
$ php hello_from_rust.php Functions available in the test extension: confirm_hello_from_rust_compiled Hello from Rust, Jared (from PHP!!)! Congratulations! You have successfully modified ext/hello_from_rust/config.m4. Module hello_from_rust is now compiled into PHP. Segmentation fault: 11
Not bad, php has entered our c extension, seen our application method list and called it. Next, the c extension has entered our rust library and started printing our string. That's fun! But...what happened to that wrong ending?
As I mentioned, the Rust-related println! macro is used here, but I did not debug it further. If we remove this from our Rust library and return a char* replacement, the segfault disappears.
这里是 Rust 的代码:
复制代码 代码如下:
#![crate_type = "staticlib"]
#![feature(libc)]
extern crate libc;
use std::ffi::{CStr, CString};
#[no_mangle]
pub extern "C" fn hello_from_rust(name: *const libc::c_char) -> *const libc::c_char {
let buf_name = unsafe { CStr::from_ptr(name).to_bytes() };
let str_name = String::from_utf8(buf_name.to_vec()).unwrap();
let c_name = format!("Hello from Rust, {}", str_name);
CString::new(c_name).unwrap().as_ptr()
}
并变更 C 头文件:
#ifndef __HELLO #define __HELLO const char * hello_from_rust(const char *name); #endif
还要变更 C 扩展文件:
PHP_FUNCTION(confirm_hello_from_rust_compiled) { char *arg = NULL; int arg_len, len; char *strg; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) { return; } char *str; str = hello_from_rust("Jared (from PHP!!)!"); printf("%s\n", str); len = spprintf(&strg, 0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "hello_from_rust", arg); RETURN_STRINGL(strg, len, 0); }
无用的微基准
那么为什么你还要这样做?我还真的没有在现实世界里使用过这个。但是我真的认为斐波那契序列算法就是一个好的例子来说明一个PHP拓展如何很基本。通常是直截了当(在Ruby中):
def fib(at) do if (at == 1 || at == 0) return at else return fib(at - 1) + fib(at - 2) end end
而且可以通过不使用递归来改善这不好的性能:
def fib(at) do if (at == 1 || at == 0) return at elsif (val = @cache[at]).present? return val end total = 1 parent = 1 gp = 1 (1..at).each do |i| total = parent + gp gp = parent parent = total end return total end
那么我们围绕它来写两个例子,一个在PHP中,一个在Rust中。看看哪个更快。下面是PHP版:
def fib(at) do if (at == 1 || at == 0) return at elsif (val = @cache[at]).present? return val end total = 1 parent = 1 gp = 1 (1..at).each do |i| total = parent + gp gp = parent parent = total end return total end
这是它的运行结果:
$ time php php_fib.php real 0m2.046s user 0m1.823s sys 0m0.207s
现在我们来做Rust版。下面是库资源:
复制代码 代码如下:
#![crate_type = "staticlib"]
fn fib(at: usize) -> usize {
if at == 0 {
return 0;
} else if at == 1 {
return 1;
}
let mut total = 1;
let mut parent = 1;
let mut gp = 0;
for _ in 1 .. at {
total = parent + gp;
gp = parent;
parent = total;
}
return total;
}
#[no_mangle]
pub extern "C" fn rust_fib(at: usize) -> usize {
fib(at)
}
注意,我编译的库rustc - O rust_lib.rs使编译器优化(因为我们是这里的标准)。这里是C扩展源(相关摘录):
PHP_FUNCTION(confirm_rust_fib_compiled) { long number; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &number) == FAILURE) { return; } RETURN_LONG(rust_fib(number)); }
运行PHP脚本:
<?php $br = (php_sapi_name() == "cli")? "":"<br>"; if(!extension_loaded('rust_fib')) { dl('rust_fib.' . PHP_SHLIB_SUFFIX); } for ($i = 0; $i < 100000; $i ++) { confirm_rust_fib_compiled(92); } ?>
这就是它的运行结果:
$ time php rust_fib.php real 0m0.586s user 0m0.342s sys 0m0.221s
你可以看见它比前者快了三倍!完美的Rust微基准!