search
HomeBackend DevelopmentPHP TutorialCreate a php extension using rust
Create a php extension using rustDec 02, 2016 am 09:41 AM
phprust

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);
}

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

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

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 "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!

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

正如我所理解的那样,这些是基本的宏命令。但是有关这些宏命令的文档是相当糟糕的(比如: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);
}

注意:我添加了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()
}

并变更 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(&#39;rust_fib&#39;)) {
    dl(&#39;rust_fib.&#39; . 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微基准!

总结

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


Statement
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
php怎么把负数转为正整数php怎么把负数转为正整数Apr 19, 2022 pm 08:59 PM

php把负数转为正整数的方法:1、使用abs()函数将负数转为正数,使用intval()函数对正数取整,转为正整数,语法“intval(abs($number))”;2、利用“~”位运算符将负数取反加一,语法“~$number + 1”。

php怎么实现几秒后执行一个函数php怎么实现几秒后执行一个函数Apr 24, 2022 pm 01:12 PM

实现方法:1、使用“sleep(延迟秒数)”语句,可延迟执行函数若干秒;2、使用“time_nanosleep(延迟秒数,延迟纳秒数)”语句,可延迟执行函数若干秒和纳秒;3、使用“time_sleep_until(time()+7)”语句。

php怎么除以100保留两位小数php怎么除以100保留两位小数Apr 22, 2022 pm 06:23 PM

php除以100保留两位小数的方法:1、利用“/”运算符进行除法运算,语法“数值 / 100”;2、使用“number_format(除法结果, 2)”或“sprintf("%.2f",除法结果)”语句进行四舍五入的处理值,并保留两位小数。

php字符串有没有下标php字符串有没有下标Apr 24, 2022 am 11:49 AM

php字符串有下标。在PHP中,下标不仅可以应用于数组和对象,还可应用于字符串,利用字符串的下标和中括号“[]”可以访问指定索引位置的字符,并对该字符进行读写,语法“字符串名[下标值]”;字符串的下标值(索引值)只能是整数类型,起始值为0。

php怎么根据年月日判断是一年的第几天php怎么根据年月日判断是一年的第几天Apr 22, 2022 pm 05:02 PM

判断方法:1、使用“strtotime("年-月-日")”语句将给定的年月日转换为时间戳格式;2、用“date("z",时间戳)+1”语句计算指定时间戳是一年的第几天。date()返回的天数是从0开始计算的,因此真实天数需要在此基础上加1。

php怎么读取字符串后几个字符php怎么读取字符串后几个字符Apr 22, 2022 pm 08:31 PM

在php中,可以使用substr()函数来读取字符串后几个字符,只需要将该函数的第二个参数设置为负值,第三个参数省略即可;语法为“substr(字符串,-n)”,表示读取从字符串结尾处向前数第n个字符开始,直到字符串结尾的全部字符。

php怎么替换nbsp空格符php怎么替换nbsp空格符Apr 24, 2022 pm 02:55 PM

方法:1、用“str_replace("&nbsp;","其他字符",$str)”语句,可将nbsp符替换为其他字符;2、用“preg_replace("/(\s|\&nbsp\;||\xc2\xa0)/","其他字符",$str)”语句。

php怎么判断有没有小数点php怎么判断有没有小数点Apr 20, 2022 pm 08:12 PM

php判断有没有小数点的方法:1、使用“strpos(数字字符串,'.')”语法,如果返回小数点在字符串中第一次出现的位置,则有小数点;2、使用“strrpos(数字字符串,'.')”语句,如果返回小数点在字符串中最后一次出现的位置,则有。

See all articles

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

AI Hentai Generator

AI Hentai Generator

Generate AI Hentai for free.

Hot Tools

SublimeText3 English version

SublimeText3 English version

Recommended: Win version, supports code prompts!

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

Integrate Eclipse with SAP NetWeaver application server.

WebStorm Mac version

WebStorm Mac version

Useful JavaScript development tools

SublimeText3 Linux new version

SublimeText3 Linux new version

SublimeText3 Linux latest version

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

This project is in the process of being migrated to osdn.net/projects/mingw, you can continue to follow us there. MinGW: A native Windows port of the GNU Compiler Collection (GCC), freely distributable import libraries and header files for building native Windows applications; includes extensions to the MSVC runtime to support C99 functionality. All MinGW software can run on 64-bit Windows platforms.