前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >在 WSL 中学习 Rust ffi

在 WSL 中学习 Rust ffi

作者头像
MikeLoveRust
发布2020-05-14 16:46:40
1.1K0
发布2020-05-14 16:46:40
举报

博主最近从新学习 Rust FFI 的使用,但是手头上没有可用的 Linux 环境(Windows 编译c太麻烦了),于是就尝试着使用 WSL来搭建 Rust 环境和简易的 c 编译环境,并记录下中间遇到的一些坑。感谢 Unsafe Rust 群群友 @框框 对本文的首发赞助!感谢 Rust 深水群 @栗子 的 gcc 指导!

阅读须知

阅读本文,你可以知道:

  • 一些配置 WSL 全局变量的技巧
  • 快速配置 Rust 编译运行环境
  • 简单的 gcc 编译技巧

但是,本文不涉及:

  • 如何安装 WSL?
  • 如何解决 WSL 中文乱码问题? 顺带一提的是,博主通过 VS Code 使用 WSL,因为 Win 10 已经配置成 UTF-8 编码,所以并没有出现乱码问题
  • Rustup 国内镜像有哪些?
  • cargo 详细使用教程
  • 甚至不会讲 Rust FFI 是什么

WSL Rust 环境搭建

由于 WSL 是新装的,没有 Rust 和 gcc/g++ 环境,因此需要安装:

sudo apt install gcc -y

# 官方脚本
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

但是由于在国内访问 Rust 官方网站会很慢,因此设置镜像到 Windows 环境变量中:

RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static
RUSTUP_UPDATE_ROOT=https://mirrors.ustc.edu.cn/rust-static/rustup

然后,使用 WSLENV环境变量将上述变量共享到 WSL 中:

WSLENV=RUSTUP_DIST_SERVER:RUSTUP_UPDATE_ROOT

然后重启 WSL 终端,重新执行 Rust 一键脚本。

以下两个项目均来自 《Rust编程之道》一书,源代码仓库在这里

Rust 调用 C/C++

Rust 调用 C/C++ 代码可以使用 cc crate 配合 build.rs 预先编译好 C/C++ 的程序提供给 Rust 调用。

首先,创建一个 binary 项目:

cargo new --bin ffi_learn

项目目录结构如下:

cpp_src
    |-- sorting.h
    |-- sorting.cpp
src
    |-- main.rs
Cargo.toml
build.rs

然后编写 sorting.hsorting.cpp:

// sorting.h
#ifndef __SORTING_H__
#define __SORTING_H__ "sorting.h"
#include <iostream>
#include <functional>
#include <algorithm>
#ifdef __cplusplus
extern "C" {
#endif

void interop_sort(int[], size_t);

#ifdef __cplusplus
}
#endif
#endif
// sorting.cpp
#include "sorting.h"

void interop_sort(int numbers[], size_t size) {
    int* start = &numbers[0];
    int* end = &numbers[0] + size;

    std::sort(start, end, [](int x, int y) { return x > y; });
}

然后给 Cargo.toml[build-dependecies] 加上 cc crate 依赖:

# Cargo.toml
# 其他配置

[build-dependencies]
cc = "1"

接着,我们通过 cc 调用对应平台的c/c++编译器,因为我们这个项目是 WSL,所以和调用我们刚安装的 gcc:

// build.rs

// Rust 2018 不需要 extern crate 语句

fn main() {
    cc::Build::new()
        .cpp(true)
        .warnings(true)
        .flag("-Wall")
        .flag("-std=c++14")
        .flag("-c")
        .file("cpp_src/sorting.cpp")
        .compile("sorting");
}

接着,我们在 Rust 主程序中,通过 extern 块引入sorting.cpp中的interop_sort函数,并调用它:

#[link(name = "sorting", kind = "static")]
extern "C" {
    fn interop_sort(arr: &[i32], n: u32);
}

pub fn sort_from_cpp(arr: &mut [i32]) {
    unsafe {
        // 通过传入 数组的长度来保证不会出现越界访问,从而保证函数内存安全
        interop_sort(arr, arr.len() as u32);
    }
}

fn main() {
    let mut my_arr: [i32; 10] = [10, 42, -9, 12, 8, 25, 7, 13, 55, -1];
    println!("Before sorting...");
    println!("{:?}\n", my_arr);

    sort_from_cpp(&mut my_arr);

    println!("After sorting...");
    println!("{:?}\n", my_arr);
}

然后执行调用:

$ cargo run
   Compiling ffi_learning v0.1.0 (/mnt/c/Users/huangjj27/Documents/codes/ffi_learning)
warning: `extern` block uses type `[i32]`, which is not FFI-safe
 --> src/main.rs:3:26
  |
3 |     fn interop_sort(arr: &[i32], n: u32);
  |                          ^^^^^^ not FFI-safe
  |
  = note: `#[warn(improper_ctypes)]` on by default
  = help: consider using a raw pointer instead
  = note: slices have no C equivalent

    Finished dev [unoptimized + debuginfo] target(s) in 4.71s
     Running `target/debug/ffi_learn`
Before sorting...
[10, 42, -9, 12, 8, 25, 7, 13, 55, -1]

After sorting...
[55, 42, 25, 13, 12, 10, 8, 7, -1, -9]

我们看到,该函数提示我们 C 中并没有等价于 Rust slice 的类型,原因在于如果我们传递 slice,那么在 C/C++ 中就很容易访问超过数组长度的内存,造成内存不安全问题。但是,我们在 Rust 调用的时候,通过同时传入数组 arr 的长度 arr.len(), 来保证函数不会访问未经授权的内存。不过在实践中,应该划分模块,只允许确认过 内存安全的 safe Rust 功能跨越模块调用。

在 C/C++ 中调用 Rust

接下来我们反过来互操作。项目结构如下:

c_src
    |-- main.c
src
    |-- lib.rs
    |-- callrust.h
Cargo.toml
makefile

然后配置 Rust 生成两种库——静态库(staticlib)和c动态库(cdylib):

# Cargo.toml
# ...

[lib]
name = "callrust"   # 链接库名字
crate-type = ["staticlib", "cdylib"]

然后添加我们的 Rust 函数:

// lib.rs

// `#[no_mangle]` 关闭混淆功能以让 C 程序找到调用的函数
// `extern` 默认导出为 C ABI
#[no_mangle]
pub extern fn print_hello_from_rust() {
    println!("Hello from rust");
}

当然,为了给 C 调用我们还需要编写一个头文件:

// callrust.h
void print_hello_from_rust();

在我们的 main.c 中库并调用:

// main.c
#include "callrust.h"
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

int main(void) {
    print_hello_from_rust();
}

编写 makefile,先调度cargo 编译出我们需要的 Rust 库(动态或链接),然后再运行:

GCC_BIN ?= $(shell which gcc)
CARGO_BIN ?= $(shell which cargo)

# 动态链接 libcallrust.so
share: clean cargo
truemkdir cbin
true$(GCC_BIN) -o ./cbin/main ./c_src/main.c -I./src -L./target/debug -lcallrust

true# 注意动态链接再运行时也需要再次指定 `.so` 文件所在目录,否则会报错找不到!
trueLD_LIBRARY_PATH=./target/debug ./cbin/main

# 静态链接 libcallrust.a
static: clean cargo
truemkdir cbin

true# libcallrust.a 缺少了一些pthread, dl类函数,需要链接进来
true$(GCC_BIN) -o ./cbin/main ./c_src/main.c -I./src ./target/debug/libcallrust.a -lpthread -ldl
true./cbin/main

clean:
true$(CARGO_BIN) clean
truerm -rf ./cbin

cargo:
true$(CARGO_BIN) build

小结

本文通过给出两个简单的示例来展示 Rust 通过 FFI 功能与 C/C++ 生态进行交互的能力, 并且指出几个在实践过程中容易浪费时间的坑:

  1. WSL的环境变量不生效 -> 使用 WSLENV 变量从 Windows 引入使用。
  2. make share 的时候提示 libcallrust.so 找不到 -> 需要在运行时指定 LD_LIBRARY_PATH 变量,引入我们编译的 libcallrust.so 路径。
  3. make static的时候遇到了pthread_* dy*系列函数未定义问题 -> 通过动态链接系统库来支持运行。

原文链接:http://huangjj27.gitlab.io/about/

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Rust语言学习交流 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 阅读须知
  • WSL Rust 环境搭建
  • Rust 调用 C/C++
  • 在 C/C++ 中调用 Rust
  • 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档