【译】用 `Wasmer` 进行插件开发 1

2023-11-16

【译】用 Wasmer 进行插件开发 1

几个月之前,Wasmer 团队发布了一个 Web Assembly(aka wasm) 解释器,用于 rust 程序的嵌入式开发。对于任何想要在项目中添加插件的人来说,这尤其令人兴奋,因为Rust提供了一种直接将程序编译到 wasm 的方法,这个应该是一个很好的选择。在这个系列的博客文章中,我们将研究如何使用 wasmerrust 构建插件系统。

环境设置

在我们深入研究细节之前,我们心里要想像一个这个项目的轮廓。如果你在电脑上继续接下来的学习,你可以做到;如果没有电脑,一切做起来可能显得没那么神奇。为此,我们将利用 cargo 的workspace特性,该特性可以让我们在一个父项目中聚合一组相关的项目。这里相关的代码你都能在 Github 仓库 上找到,每个分支代表这个系列的不同状态。我们要研究的基本结构是这样的:

wasmer-plugin-example
├── Cargo.toml
├── crates
│   ├── example-macro
│   │   ├── Cargo.toml
│   │   └── src
│   │       └── lib.rs
│   ├── example-plugin
│   │   ├── Cargo.toml
│   │   └── src
│   │       └── lib.rs
│   └── example-runner
│       ├── Cargo.toml
│       └── src
│           └── main.rs
└── src
    └── lib.rs
  • wasmer-plugin-example 是一个rust库,我们将在下一个部分谈论其中的细节。
  • crates 这个目录将会存放我们所有其他的项目
  • example-plugin 用于测试插件以保证运行结果是我们期望的
  • example-runner 二进制项目,将会作为插件的host
  • example-macro 一个 proc_macro 库,将会在下一个部分文章中进行创建

为此,我们将从创建父项目开始。运行以下命令:

cargo new --lib wasmer-plugin-example
cd wasmer-plugin-example

一旦我们创建了这个目录,我们就切换到那个目录中,然后用你选择好的编辑器打开,然后打开其中的 Cargo.toml 文件。我们需要将 [workspace] 增加到配置中,并指向到上方所说的 crate 库中的 3 个项目。配置内容参考如下所示:

[package]
name = "wasmer-plugin-example"
version = "0.1.0"
authors = ["freemasen <r@wiredforge.com>"]
edition = "2018"

[dependencies]


[workspace]
members = [
    "./crates/example-macro",
    "./crates/example-plugin",
    "./crates/example-runner",
]

现在我们可以在当前目录中创建crates目录和项目:

mkdir ./crates
cd ./crates
cargo new --lib example-plugin
cargo new --lib example-macro
cargo new example-runner

这样,我们就设置好了工作目录。我们可以在该项目内的任意目录中使用 cargo 命令,以及工作区中的任意项目中构建目标。我们用 -p 参数来告诉 cargo 我们想要应用的项目。例如我们想要构建 example-plugin 项目,可以使用下面的命令:

cargo build -p example-plugin

在我们所有工作空间中,为了设置好开发环境,我们会花一些时间。首先,大多数情况下,我们需要有rust编译器,cargorustup 。如果你需要这些,可以去 rustup.rs 。在我们安装了这些工具后,我们需要用 rustup 来构建目标 web assembly

rustup target add wasm32-unknown-unknown

除了 rust 所必须的之外,我们还需要 wasmer 相关的东西。完整的指南可以在这里找到,对于大多数系统,你可能只需要确认安装了 cmake 即可,对于windows,可能稍微复杂一些,但依赖指南上有链接地址。

第一个插件

把上面说的先放一边,我们该进入正题了,Web Assembly 的规范仅仅允许数值的存在。值得庆幸的是,在 Rust 中的 web assembly 已经可以为我们处理这个问题,但我们想要调用插件中的功能,只需要接收数字作为参数,并且只返回数字。记住这个规范,我们先以一个非常简单的实例作为开始。我会记录这个示例,虽然不会很有帮助,但我保证我们将会逐渐提升能力来做更多有趣的东西。

// ./crates/example-plugin/src/lib.rs
#[no_mangle]
pub fn add(one: i32, two: i32) -> i32 {
    one + two
}

上方示例看起来是一个非常原生并且没有意义的示例,但它符合我们的只处理数值的需求。现在我们开始编译成为 Web Assembly,我们需要在 Cargo.toml 文件中设置一些东西。

# ./crates/example-plugin/Cargo.toml
[package]
name = "example-plugin"
version = "0.1.0"
authors = ["freemasen <r@wiredforge.com>"]
edition = "2018"

[dependencies]


[lib]
crate-type = ["cdylib"]

这里比较关键的是 crate-type = ["cdylib"],它表示我们将会编译这个 crate 库生成一个 c 动态链接库。现在我们使用下面的命令进行编译:

cargo build --target wasm32-unknown-unknown

到这里,我们应该有一个文件位于: ./target/wasm32-unknown-unknown/debug/example_plugin.wasm 。现在,让我们构建一个可以运行这个的程序,第一步我们将声明好所有依赖。

第一个运行器

# ./crates/example-runner/Cargo.toml
[package]
name = "example-runner"
version = "0.1.0"
authors = ["freemasen <r@wiredforge.com>"]
edition = "2018"

[dependencies]
wasmer_runtime = "0.3.0"

这里,我们增加了 wamer_runtime 库,我们将用它连接我们的 web assembly 模块。

// ./crates/example-runner/src/main.rs
use wasmer_runtime::{
    imports,
    instantiate,
};
// 现在,我们使用它来读取 wasm 字节
static WASM: &[u8] = include_bytes!("../../../target/wasm32-unknown-unknown/debug/example_plugin.wasm");

fn main() {
    // 实例化 web assembly 模块
    let instance = instantiate(WASM, &imports!{}).expect("failed to instantiate wasm module");
    // 绑定模块中的 add 函数
    let add = instance.func::<(i32, i32), i32>("add").expect("failed to bind function add");
    // 调用 add 函数
    let three = add.call(1, 2).expect("failed to execute add");
    println!("three: {}", three); // "three: 3"
}

首先,我们使用 use 语句,导入其中的 2 个方法实现;imports 宏简单地用于定义导入的对象以及将字节转换为 web assembly 模块的 instantiate 函数。现在,我们将会使用 include_bytes! 宏,使用它来读入我们的字节,但我们想要它更灵活一些。在 main 函数中,我们将通过 2 个参数调用 instantiate,第一个参数是 wasm 字节,第二个是空的导入对象。接下来,我们将使用 instantiate 中的 func 去绑定 add 函数,该函数有两个 i32 类型参数和一个 i32 类型的返回值。此时,我们可以通过 call 调用函数中的 add 方法。然后打印结果到终端。当我们使用 cargo 命令运行它,应该会成功地打印 three: 3 出现在终端中。

耶,成功了!但那并没什么卵用。让我们研究一下,我们需要让它更有用一些。

深入探究

我们需要

  • 运行函数前需要访问 WASM 内存
  • 一个插入更加复杂的数据结果到内存中的方法
  • 一个方法,wasm 模块将在何处与什么样的数据通信
  • 插件被执行后,从 wasm 内存中获取更新信息的系统

首先,运行我们的功能之前,我们需要一个方法来初始化 wasm 模块内存中一些值。幸运的是,wasmer_runtime 提供给我们一个完整的方法。让我们更新我们的实例,给定在一个字符串,返回字符串的长度,这跟上面的小例子相比,并没有好到哪儿去,但。。。一步一步来吧。

我们的第 2 个插件

// ./crates/example-plugin/src/lib.rs

// 如果这是纯 Rust 交互,我们的代码就是如下所示:
pub fn length(s: &str) -> u32 {
    s.len() as u32
}

// 因为我们不需要将数据从 wasm 转换到 rust 中
#[no_mangle]
pub fn _length(ptr: i32, len: u32) -> u32 {
    // 从内存中获取字符串
    let value = unsafe { 
        let slice = ::std::slice::from_raw_parts(ptr as _, len as _);
        String::from_utf8_lossy(slice)
    };
    // 传递值到 `length` 中,并返回结果
    length(&value)
}

这一次,我们需要做的还有很多,让我们回顾一下发生了什么。首先,我们定义了一个 length 的函数,如果我们从另一个 rust 程序中使用这个库,这正是我们想要的。一旦我们使用这个库作为一个 wasm 模块,我们需要增加一个处理内存交互的辅助方法。这看起来似乎是一个奇怪的结构,但这样做,可以提供额外的灵活性,随着我们的深入,这种灵活性会更加明显。_length 函数就能起到这个辅助作用。首先,我们需要参数和返回值来匹配跨越 wasm 边界时可用的值(只能是数值)。然后,我们的参数将是存放字符串的东西,ptr 是这个字符串的开头部分,并且 len 是字符串的长度。因为我们处理的是原始内存,所以我们需要在一个 unsafe 的代码块内进行转换(我知道这有点吓人,但我们要确保运行器中确实有这个字符串)。一旦我们将字符串从内存中取出,就可以像平常一样将其传递到 length,然后返回结果。继续像之前那样进行构建。

cargo build --target wasm32-unknown-unknown

现在我们看看如何在启动文件中设置它。

// ./crates/example-runner/src/main.rs
use wasmer_runtime::{
    imports,
    instantiate,
};

// 现在,我们将使用它读取 wasm 字节
static WASM: &[u8] = include_bytes!("../../../target/wasm32-unknown-unknown/debug/example_plugin.wasm");

fn main() {
    let instance = instantiate(&WASM, &imports!{}).expect("failed to instantiate wasm module");
    // 代码修改从这开始的,首先获取模块的上下文
    let context = instance.context();
    // 然后从 web assembly 上下文中得到起始内存 0,只支持一个内存块,所以它一直为 0
    let memory = context.memory(0);
    // 现在,我们可以获取内存的 view
    let view = memory.view::<u8>();
    // 这是我们要传递到 wasm 中的字符串
    let s = "supercalifragilisticexpialidocious".to_string();
    // string 转换为 bytes
    let bytes = s.as_bytes();
    // bytes 的长度
    let len = bytes.len();
    // 循环 wasm 内存 view 中的字节和字符串字节
    for (cell, byte) in view[1..len + 1].iter().zip(bytes.iter()) {
        // 将 wasm 内存中的字节设为字符串的字节值
        cell.set(*byte)
    }
    // 绑定辅助方法
    let length = instance.func::<(i32, u32), u32>("_length").expect("Failed to bind _length");
    let wasm_len = match length.call(1 as i32, len as u32) {
        Ok(l) => l,
        Err(e) => panic!("{}\n\n{:?}", e, e),
    }; //.expect("Failed to execute _length");
    println!("original: {}, wasm: {}", len, wasm_len); // original: 34, wasm: 34
}

好了,这次我们需要做更多的事情。开始的几行,跟之前完全一样,我们将读取 wasm 并且实例化它。一旦加载完成,我们将获得 wasm 内存中的 view,我们首先从模块实例中获得 Ctx (context) 。一旦有了上下文,我们就可以通过调用 memory(0),web assembly 目前仅有一个内存区域,所以在短时间内,它的值总是 0,但以后可能允许有多块内存区域。获取原始内存的最后一步是调用 view() 方法,我们终于可以修改模块的内存了。view的类型是 Vec<Cell<u8>>,因此我们有一个字节数组,但是每个字节都包装在一个 Cell 中。根据文档中,通过 Cell 的方式可以修改本来的不可变值,在我们的场景中,它的意思是:“我不会让这个内存变长或变短,只是更改它的值”。

现在,我们定义传递给 wasm 内存的字符串,并将其转换为字节数组。我们还想跟踪字符串的字节长度,因此我们将其捕获它作为 len。要将字符串的字节数组放入内存字节中,我们将使用 Zip 迭代器,这让我们循环时能同时得到 2 个值。在每次迭代中,会在 cell 索引等于字符字节索引时停止,在循环体中,我们将 wasm 内存字节的值赋值为字符串字节的值。注意,我们从 view 的索引 1 开始,这意味着我们的 ptr 参数是 1,并且我们的字节长度将是 len 参数。

cargo run
original: 34, wasm: 34

耶,再次成功了!但是,仍然没什么卵用。但是它将给我们处理复杂数据提供了一个良好的基础。在第2部分中,我们将了解如何与等式两边的 wasm 内存进行交互。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【译】用 `Wasmer` 进行插件开发 1 的相关文章

  • Django - 该进程无法访问该文件,因为该文件正在被另一个进程使用

    我正在尝试在 Windows 10 上运行 Django 我是 Django 的新手 我正在使用 Compressor Toolkit 我的问题是 我可以运行 manage py 但本地主机说 base html 第 9 行出错该进程无法访
  • Tkinter 按钮鼠标右键和左键单击有不同的命令

    我正在用 Python 制作扫雷游戏 并使用 tkinter 库来创建 gui 有没有 绑定到 tkinter 按钮两个命令的方法 一个是右键单击按钮时的命令 另一个是单击左键时的命令 通常 按钮仅设计用于单击 但 tkinter 允许您为
  • 使用 https 的 Java Jersey RESTful Web 服务

    我是 Java EE 的新手 正在开发一个 RESTful API 其中每个 API 调用用户都会发送编码的凭据 我的问题是如何通过默认的 http 实现 https 协议并确保我的连接安全 我正在使用 Jersey Restful Web
  • 如果在构造函数中使用 super 调用重写方法会发生什么

    有两个班级Super1 and Sub1 超1级 public class Super1 Super1 this printThree public void printThree System out println Print Thre
  • 如何避免在matplotlib中调用latex(输出到pgf)

    我使用 matplotlib 及其 pgf 后端来生成包含在 LaTeX 投影仪文档中的绘图 当我使用未定义的乳胶命令时 我遇到了麻烦 但对于我的应用程序 我不需要 matplotlib 来使用 Latex 生成标签或注释 我只想要正确的
  • Kotlin 中的枚举类对于 Android 来说是否像 Java 中那样“昂贵”?

    Are EnumKotlin 中的类对于 Android 来说 昂贵 就像 Java 一样 还可以用吗 IntDefs or StringDefs在科特林 当我将 Kotlin Enum 类反编译为 Java 类时 底层仍然使用了 Java
  • 我无法设置顶级标题

    我想为 TopLevel 设置标题 但 TopLevel 显示 Root 的标题 我认为我的下一个脚本与 TkInter 文档中的示例相对应 但给了我不好的结果 你能解释一下 为什么我的设置master title 顶部 in 应用程序顶部
  • 如何在 PyTorch 中对子集使用不同的数据增强

    如何针对不同的情况使用不同的数据增强 转换 Subset在 PyTorch 中吗 例如 train test torch utils data random split dataset 80000 2000 train and test将具
  • 如何在打开导航抽屉时使背景 Activity 变小?

    我想做我的背景Activity打开时稍微小一点Navigation Drawer 模拟存在的效果Airbnb应用 我想最好的解释是截图 但重点不是让 View 变小 而是让它成为与 Drawer 打开 关闭动画同步的动画 因此 如果您开始打
  • Android:如何停止监听电话监听器? [复制]

    这个问题在这里已经有答案了 可能的重复 Android 为什么 PhoneCallListener 在活动完成后仍然存在 https stackoverflow com questions 11666853 android why phon
  • 如何在 Google 地图中创建自定义地图?

    我正在尝试创建一个包含我家地图的 Google 地图应用程序 卧室 浴室 厨房等 使用 GPS 我会找到我现在在家里的位置 并尝试获取到我卧室的方向 步行距离 您可以使用Google的API来获取方向 我需要知道的是 如何添加我家的自定义地
  • android中ScrollView中的图像

    在我的应用程序中 我想放置一个 png 文件 并且希望它在横向和纵向模式下都被视为滚动图像 请建议代码或示例 要使您的 Imageview 在高度不适合时滚动 您可以在 xml 中的 ScrollView 内添加一个 ImageView 并
  • Pandas 使用什么规则来生成视图和副本?

    我对 Pandas 在决定数据帧中的选择是原始数据帧的副本或原始数据帧的视图时使用的规则感到困惑 例如 如果我有 df pd DataFrame np random randn 8 8 columns list ABCDEFGH index
  • 检测反射 DLL 注入

    在过去的几年中 恶意软件 以及一些渗透测试工具 如 Metasploit 的 meterpreter 负载 已经开始使用反射 DLL 注入 PDF http www harmonysecurity com files HS P005 Ref
  • 访问 Scrapy 内的 django 模型

    是否可以在 Scrapy 管道内访问我的 django 模型 以便我可以将抓取的数据直接保存到我的模型中 我见过this https scrapy readthedocs org en latest topics djangoitem ht
  • gstreamer 中的无缝视频循环

    我正在尝试使用 gstreamer 循环播放视频 它是 python 绑定 第一次尝试是hook EOSmessage并为管道生成搜索消息 import gi gi require version Gst 1 0 from gi repos
  • 有没有比 Python 内置 == 运算符更快的方法来测试两个列表是否具有完全相同的元素?

    如果我有两个列表 每个列表有 800 个元素长并填充整数 有没有比使用内置元件更快的方法来比较它们具有完全相同的元件 如果没有 则短路 操作员 a 6 2 3 88 54 486 b 6 2 3 88 54 486 a b gt gt gt
  • *Python 内的 Kaggle API 文档?

    我想写一个python从 Kaggle com 下载公共数据集的脚本 Kaggle API 是用 python 编写的 但是我能找到的几乎所有文档和资源都是关于如何在命令行中使用该 API 的 而关于如何使用kaggle图书馆内python
  • 如何在服务器上获取球衣日志?

    我正在使用球衣进行 REST WS 如何在服务器端启用球衣日志 很长的故事 我收到客户端异常 但我在 tomcat 日志中没有看到任何内容 它甚至没有到达我的方法 由于堆栈跟踪显示 toReturnValue 它确实从服务器获取了一些内容
  • 如何测试send_file烧瓶

    我有一个小型烧瓶应用程序 它需要上传一些图像并将它们转换为多页 tiff 没什么特别的 但是如何测试多个文件的上传和文件下载呢 我的测试客户端 class RestTestCase unittest TestCase def setUp s

随机推荐

  • Linux KVM 使用教程(一)

    文章目录 1 KVM简介 2 KVM 的功能列表 3 KVM 工具集合 3 1 Virsh命令 1 KVM简介 1 KVM 全称是 基于内核的虚拟机 Kernel based Virtual Machine 它是Linux 的一个内核模块
  • python2(基本)

    实验02 基本 一 课内实验题 共10小题 100分 题型得分 100 描述 编写程序 从键盘输入两个整数 计算并输出这两个整数的和 平均值 最小值和最大值 平均值保留2位小数 输入 分行输入两个整数 输出 分行输出两个整数的和 平均值 最
  • JDK1.8 下载与安装

    JDK安装 JDK1 8下载 下载链接 https www oracle com java technologies javase javase jdk8 downloads html 根据操作系统版本下载 这里以win10 64位操作系统
  • 驱动程序里ioctl下switch问题

    今天在写步进电机驱动程序时 switch语句引出3个分支 case 0 case 1 case 2 case 0 什么都不做 case 1让步进电机正向转动 case 2让步进电机反向转动 但是测试时 case 2怎么也动不起来 后来把ca
  • PLSQL Developer的配置方法

    1 下载32位的版本instantclient basic nt 11 2 0 3 0 zip 因为PLSQLDev是32位的 没有64位的版本 这 个和操作系统无关 2 instantclient下载完后是一个压缩文件 不需要安装 配置一
  • 服务器系统如何清理,服务器清理内存怎么清理

    服务器清理内存怎么清理 内容精选 换一换 本节操作指导您完成Windows操作系统云服务器磁盘空间清理 弹性云服务器匀出一部分磁盘空间来充当内存使用 当内存耗尽时 云服务器可以使用虚拟内存来缓解内存的紧张 但当内存使用率已经非常高时 频繁的
  • 关于HTTP协议,一篇就够了

    HTTP简介 HTTP协议是Hyper Text Transfer Protocol 超文本传输协议 的缩写 是用于从万维网 WWW World Wide Web 服务器传输超文本到本地浏览器的传送协议 HTTP是一个基于TCP IP通信协
  • TCP 连接管理机制(一)——TCP三次握手详解 + 为什么要有三次握手

    TCP是面向连接的协议 在通信之前需要先建立连接 其本质就是打开一个socket文件 这个文件有自己的缓冲区 如果要发送数据 上层把数据拷贝到发送缓冲区 如果是接收数据 OS直接把来自网络的数据拷贝到接收缓冲区里 那么三次握手期间 Serv
  • youversion.com的圣经无法使用、无法连接、无法下载离线版本的解决方法

    最近 youversion com的圣经无法使用 无法连接 无法下载离线版本了 这是一部很好用的圣经软件 以前一直用着 后来ipad越狱重新安装的时候就不能连接了 后来无意间发现原来是这个网站被和谐了 至于GCD为什么这么做 以咱的智商尚不
  • 接口自动化测试须知

    一 做接口测试需要哪些技能 做接口测试 需要的技能 基本就是以下几点 业务流 了解系统及内部各个组件之间的业务逻辑交互 数据流 了解接口的I O input output 输入输出 协议 包括http协议 TCP IP协议族 http协议
  • CMD查杀端口的两种方式

    第一种 netstat ano windows r输入cmd并打开 输入netstat ano 记住对应的6052 输入杀掉端口 taskkill pid 6052 f 第二种 netstat aon findstr 8080 直接输入ne
  • Win10 + VS2017 + Ceres配置

    前言 Ceres是google出品的一款基于C 的开源非线性优化库 官方文档 Ceres官方文档地址 依赖库 Eigen 官网 glog github gflags github Ceres github 配置过程 1 Eigen Eige
  • Python3 爬虫 requests+BeautifulSoup4(BS4) 爬取小说网站数据

    刚学Python爬虫不久 迫不及待的找了一个网站练手 新笔趣阁 一个小说网站 前提准备 安装Python以及必要的模块 requests bs4 不了解requests和bs4的同学可以去官网看个大概之后再回来看教程 爬虫思路 刚开始写爬虫
  • GPT专业应用:快速生成职位描述(JD)

    正文共 814 字 阅读大约需要 3 分钟 人力资源必备技巧 您将在3分钟后获得以下超能力 快速生成职位描述 Beezy评级 B级 经过简单的寻找 大部分人能立刻掌握 主要节省时间 推荐人 Kim 编辑者 Linda 图片由 Lexica
  • 数据中台与传统大数据平台有什么区别?_光点科技

    一 数据中台 数据中台是聚合和治理跨域数据 将数据抽象封装成服务 提供给前台以业务价值的逻辑概念 数据中台是在平台概念上的升级 不再单纯的将功能进行大杂烩 理念上 中台有几个特点 第一 更强调数据集中存储 统一管理 提供标准化的服务 第二
  • 【毕业设计】基于springboot + vue微信小程序商城

    目录 前言 创新点 亮点 毕设目录 一 视频展示 二 系统介绍 三 项目地址 四 运行环境 五 设计模块 前台 后台 六 系统功能模块结构图 数据库设计 七 准备阶段 使用真实支付 使用模拟支付 八 使用说明 九 登录后台 十 后台页面展示
  • 前端常用工具库方法整理

    欢迎点击领取 前端面试题进阶指南 前端登顶之巅 最全面的前端知识点梳理总结 前言 在闲余的时间整理一份我们可能用到的前端工具库方法 依赖库 名称 cropperjs 图片裁剪 exif js lrz 图片旋转问题 html2canvas d
  • React性能优化(完整版)

    我的博客 http wangxince site my demo markdown React 性能优化 1 减少 render 次数 shouldComponentUpdate PureComponent shouldComponentU
  • 计算机学习三宗罪——计算机达人成长之路(3)(转载自朱云翔老师笔记)

    以计算机学习不可浮躁 只有用心学习 深挖知识 才能基础扎实 才可以深入理解计算机专业知识 从而达到 他强由他强 清风拂山岗 他横由他横 明月照大江 的境界 万变不离其宗 编程程序具有三重境界 同样以VCD播放器为例 第一重境界就如同上面的同
  • 【译】用 `Wasmer` 进行插件开发 1

    译 用 Wasmer 进行插件开发 1 Using Wasmer for Plugins Part 1 译文 原文链接 https wiredforge com blog wasmer plugin pt 1 index html 原文 G