WEBKT

Rust meets C++:用 Rust 编写 WASM 模块并在 C++ 项目中使用

114 0 0 0

Rust meets C++:用 Rust 编写 WASM 模块并在 C++ 项目中使用

想用 Rust 的高性能和安全性,又不想放弃 C++ 项目的现有代码库?没问题!将 Rust 代码编译成 WebAssembly (WASM) 模块,然后在 C++ 项目中调用它,这是一种非常实用的解决方案。本文将手把手教你如何实现。

为什么选择 WASM?

WASM 是一种可移植的、体积小、加载快并且可以在浏览器和非浏览器环境中运行的二进制指令格式。它天然具有跨平台性,非常适合作为不同语言之间互操作的桥梁。

准备工作

  1. 安装 Rust 和 Cargo: 确保你已经安装了 Rust 编程语言和 Cargo 包管理器。你可以从 https://www.rust-lang.org/ 下载并安装。

  2. 安装 wasm-pack: wasm-pack 是一个用于构建、测试和发布 WASM 包的工具。使用 Cargo 安装它:

    cargo install wasm-pack
    
  3. 安装 Emscripten (可选,但推荐): 虽然不是必须的,但 Emscripten 可以帮助你更方便地在 C++ 中加载和使用 WASM 模块。从 https://emscripten.org/ 下载并安装。

步骤 1:创建 Rust WASM 模块

  1. 创建一个新的 Rust 项目:

    cargo new --lib rust_wasm
    cd rust_wasm
    
  2. 修改 Cargo.toml 文件: 添加 wasm-bindgen 依赖,并指定 crate 类型为 cdylibcdylib 类型会生成一个可以被 C 语言链接器使用的动态链接库。

    [package]
    name = "rust_wasm"
    version = "0.1.0"
    edition = "2021"
    
    [lib]
    crate-type = ["cdylib"]
    
    [dependencies]
    wasm-bindgen = "0.2"
    
  3. 编写 Rust 代码:src/lib.rs 文件中编写你的 Rust 代码。例如,创建一个简单的函数,用于将两个数字相加:

    use wasm_bindgen::prelude::*;
    
    #[wasm_bindgen]
    pub fn add(a: i32, b: i32) -> i32 {
        a + b
    }
    

    #[wasm_bindgen] 属性宏会将 Rust 函数暴露给 JavaScript (以及其他 WASM host 环境)。

  4. 构建 WASM 模块: 使用 wasm-pack 构建项目:

    wasm-pack build --target web
    

    这将在 pkg 目录下生成 WASM 模块 (.wasm 文件) 和 JavaScript 接口文件 (.js 文件)。

步骤 2:在 C++ 项目中使用 WASM 模块

  1. 创建一个 C++ 项目: 如果你已经有 C++ 项目,可以跳过此步骤。否则,创建一个新的 C++ 项目。

  2. 包含必要的头文件: 如果使用 Emscripten,你需要包含 Emscripten 提供的头文件。例如,创建一个 wasm_wrapper.h 文件:

    #ifndef WASM_WRAPPER_H
    #define WASM_WRAPPER_H
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    // 声明 Rust 函数 (需要根据实际情况修改)
    int add(int a, int b);
    
    #ifdef __cplusplus
    }
    #endif
    
    #endif
    

    注意: 你需要根据 Rust 函数的签名来声明 C 函数。extern "C" 确保 C++ 编译器使用 C 的调用约定,这对于与 WASM 模块交互至关重要。

  3. 加载 WASM 模块: 使用 Emscripten 或其他 WASM 运行时加载 WASM 模块。以下是使用 Emscripten 的一个示例:

    #include <iostream>
    #include <emscripten.h>
    #include "wasm_wrapper.h"
    
    int main() {
        // 初始化 Emscripten (如果需要)
        // emscripten_initialize();
    
        // 调用 Rust 函数
        int result = add(5, 3);
    
        std::cout << "Result from WASM: " << result << std::endl;
    
        // 关闭 Emscripten (如果需要)
        // emscripten_exit(0);
    
        return 0;
    }
    

    注意: emscripten_initialize()emscripten_exit() 可能不是必需的,具体取决于你的 Emscripten 配置和 WASM 模块的需求。

  4. 编译和链接 C++ 代码: 使用 C++ 编译器编译你的代码,并将 WASM 模块链接到你的项目中。 使用 Emscripten,你可以这样编译:

    emcc main.cpp -o main.js rust_wasm/pkg/rust_wasm.wasm -s EXPORTED_FUNCTIONS="['_add']"
    
    • main.cpp 是你的 C++ 代码文件。
    • main.js 是 Emscripten 生成的 JavaScript 文件,它会加载和运行 WASM 模块。
    • rust_wasm/pkg/rust_wasm.wasm 是你的 WASM 模块的路径。
    • -s EXPORTED_FUNCTIONS="['_add']" 指定要导出的 WASM 函数。 非常重要: Emscripten 会在导出的函数名前面加上下划线 (_)。
  5. 运行 C++ 代码: 使用 Node.js 或浏览器运行生成的 JavaScript 文件:

    node main.js
    

    你应该能看到 Rust 函数的返回值打印到控制台。

遇到的问题和解决方案

  • 类型不匹配: 确保 Rust 和 C++ 之间的数据类型匹配。例如,Rust 的 i32 对应于 C++ 的 int
  • 内存管理: WASM 模块和 C++ 代码可能需要共享内存。你需要仔细管理内存,以避免内存泄漏或崩溃。可以使用 wasm-bindgen 提供的 JsValueArrayBuffer 来进行更复杂的数据交互。
  • 错误处理: 在 Rust 代码中处理错误,并将错误信息传递给 C++ 代码。可以使用 Result 类型来表示可能发生的错误。
  • Emscripten 配置: Emscripten 的配置可能会比较复杂。参考 Emscripten 的文档,了解如何正确配置 Emscripten。

高级用法

  • 使用 wasm-bindgen 处理更复杂的数据结构: wasm-bindgen 可以自动生成 Rust 和 JavaScript 之间的绑定代码,从而简化数据结构的传递。
  • 使用 wee_alloc 优化 WASM 模块的大小: wee_alloc 是一个为 WASM 设计的内存分配器,可以显著减小 WASM 模块的大小。
  • 使用 Rust 的 #[cfg(target_arch = "wasm32")] 属性: 可以使用此属性来编写特定于 WASM 平台的代码。

总结

将 Rust 代码编译成 WASM 模块并在 C++ 项目中使用,是一种强大的技术,可以让你充分利用 Rust 的优势,同时保留现有的 C++ 代码库。虽然配置过程可能比较复杂,但一旦配置完成,你就可以轻松地在 Rust 和 C++ 之间进行互操作。记住,仔细处理数据类型匹配、内存管理和错误处理是成功的关键。

希望本文能帮助你入门!祝你编码愉快!

码农小李 RustWASMC++

评论点评