Create a php extension using rust

伊谢尔伦
Release: 2023-03-02 19:46:01
Original
1560 people have browsed it

Last October, my colleagues at Etsy and I had a discussion about how to write extensions for interpreted languages ​​like PHP. Ruby or Python should be easier than PHP at the moment. We talked about how the barrier to writing a successful extension is that they usually need to be written in C, but it's hard to have that confidence if you're not good at C.

Since then I came up with the idea of ​​writing one in Rust and have been trying it out for the past few days. I finally got it running this morning.

Rust in C or PHP

My basic starting point is to write some compilable Rust code into a library, and 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 foreign 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);
}
Copy after login

I split it from a Rust library called from C (or other!). Here's a good explanation of what comes next.

Compile it and you 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
Copy after login

This is what the Rust compiler tells us to link when we don’t use this dependency.

Calling Rust from C

Now that we have a library, we have to do two things to ensure that it is 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
Copy after login

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!");
}
Copy after login

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
Copy after login

Note that the -lSystem -lpthread -lc -lm at the end tells gcc not to link against those "local antiques" so that the Rust compiler can when compiling our Rust library Offer it out.

By running the following code we can get a binary file:

$ ./hello_c
Hello from Rust, Jared!
Copy after login

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

That part took me a while to figure out and the documentation isn't 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. In order to make the code run, I worked very hard to learn the php documentation, "Extended Bones".

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 code needed to create the php extension Basic skeleton. Now, move the folders everywhere you want to keep your extensions locally. And move your

.rust source

.rust library

.c header

into 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

A 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
Copy after login

正如我所理解的那样,这些是基本的宏命令。但是有关这些宏命令的文档是相当糟糕的(比如:google”PHP_ADD_LIBRARY_WITH_PATH”并没有出现PHP团队所写的结果)。我偶然这个PHP_ADD_LIBRARY_PATH宏命令在有些人所谈论的在一个PHP拓展里链接一个静态库的先前的线程里。在评论中其它的推荐使用的宏命令是在我运行ext_skel后产生的。

既然我们进行了配置设置,我们需要从PHP脚本中实际地调用库。为此我们得修改自动生成的文件,hello_from_rust.c。首先我们添加hello_from_rust.h头文件到包含命令中。然后我们要修改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);
}
Copy after login

注意:我添加了hello_from_rust(“Jared (fromPHP!!)!”);。

现在,我们可以试着建立我们的扩展:

$ phpize
$ ./configure
$ sudo make install

就是它,生成我们的元配置,运行生成的配置命令,然后安装该扩展。安装时,我必须亲自使用sudo,因为我的用户并不拥有安装目录的 php 扩展。

现在,我们可以运行它啦!

$ 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

还不错,php 已进入我们的 c 扩展,看到我们的应用方法列表并且调用。接着,c 扩展已进入我们的 rust 库,开始打印我们的字符串。那很有趣!但是……那段错误的结局发生了什么?

正如我所提到的,这里是使用了 Rust 相关的 println! 宏,但是我没有对它做进一步的调试。如果我们从我们的 Rust 库中删除并返回一个 char* 替代,段错误就会消失。

这里是 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()
}
Copy after login

并变更 C 头文件:

#ifndef __HELLO
#define __HELLO
const char * hello_from_rust(const char *name);
#endif
Copy after login

还要变更 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);
}
Copy after login

无用的微基准

那么为什么你还要这样做?我还真的没有在现实世界里使用过这个。但是我真的认为斐波那契序列算法就是一个好的例子来说明一个PHP拓展如何很基本。通常是直截了当(在Ruby中):

def fib(at) do
    if (at == 1 || at == 0)
        return at
    else
        return fib(at - 1) + fib(at - 2)
    end
end
Copy after login

而且可以通过不使用递归来改善这不好的性能:

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
Copy after login
Copy after login

那么我们围绕它来写两个例子,一个在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
Copy after login
Copy after login

这是它的运行结果:

$ time php php_fib.php
real    0m2.046s
user    0m1.823s
sys 0m0.207s
Copy after login

现在我们来做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)
}
Copy after login

注意,我编译的库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));
}
Copy after login

运行PHP脚本:

<?php
$br = (php_sapi_name() == "cli")? "":"<br>";
if(!extension_loaded(&#39;rust_fib&#39;)) {
    dl(&#39;rust_fib.&#39; . PHP_SHLIB_SUFFIX);
}
for ($i = 0; $i < 100000; $i ++) {
    confirm_rust_fib_compiled(92);
}
?>
Copy after login

这就是它的运行结果:

$ time php rust_fib.php
real    0m0.586s
user    0m0.342s
sys 0m0.221s
Copy after login

你可以看见它比前者快了三倍!完美的Rust微基准!

总结

这里几乎没有得出什么结论。我不确定在Rust上写一个PHP的扩展是一个好的想法,但是花费一些时间去研究Rust,PHP和C,这是一个很好的方式。


Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template