【问题标题】:Webassembly: possible to have shared objects?Webassembly:可能有共享对象吗?
【发布时间】:2021-08-11 18:43:01
【问题描述】:

我想知道,使用 C(或 C++ 或 Rust)和 javascript,我是否能够对共享数据对象执行 CRUD 操作。使用最基本的示例,这里将是一个示例或每个操作:

#include <stdio.h>
typedef struct Person {
    int age;
    char* name;
} Person;

int main(void) {

    // init
    Person* sharedPersons[100];
    int idx=0;

    // create
    sharedPersons[idx] = (Person*) {12, "Tom"};

    // read
    printf("{name: %s, age: %d}", sharedPersons[idx]->name, sharedPersons[idx]->age);

    // update
    sharedPersons[idx]->age = 11;

    // delete
    sharedPersons[idx] = NULL;

}

然后,我希望能够在 Javascript 中做完全相同的事情,并且都能够写入同一个共享的 sharedPersons 对象。怎么可能做到这一点?或者设置是否需要类似于“主从”,其中一个只需要将信息传回给另一个,而主控会执行所有相关操作?我希望有一种方法可以对 webassembly 中的共享数据对象进行 CRUD,任何帮助将不胜感激。

作为参考:https://rustwasm.github.io/wasm-bindgen/contributing/design/js-objects-in-rust.html

【问题讨论】:

  • 我只能说 Rust+wasmbindgen 流程,但是由于 webassembly 使用不同的内存空间,所以创建的任何对象都保留在各自的内存中。跨越该边界,仅复制原语(整数、字符串),其他任何内容都使用代理。
  • @kmdreko 我明白了:所以 rust 端无法写入 js 端,反之亦然,也没有共享内存空间之类的东西? “代理”是什么意思?
  • 这个的用例是什么?如果你导出你的 CRUD 函数,你可以从 javascript 调用它们;为什么你会需要 javascript 来亲自弄乱内存? (我确定一个用例,我只是觉得很难想到一个。)

标签: javascript c++ c rust webassembly


【解决方案1】:

创建对象

让我们在 C 中创建对象并返回它:

typedef struct Person {
    int age;
    char* name;
} Person;

Person *get_persons(void) {
    Person* sharedPersons[100];
    return sharedPersons;
}

您也可以在 JS 中创建对象,但更难。我稍后会回到这个。

为了让 JS 获取对象,我们定义了一个函数 (get_persons) 来返回(指向)它的指针。在这种情况下,它是一个数组,但当然它也可以是一个对象。问题是,必须有一个从 JS 调用并提供对象的函数。

编译程序

emcc \
    -s "SINGLE_FILE=1" \
    -s "MODULARIZE=1" \
    -s "ALLOW_MEMORY_GROWTH=1" \
    -s "EXPORT_NAME=createModule" \
    -s "EXPORTED_FUNCTIONS=['_get_persons', '_malloc', '_free']" \
    -s "EXPORTED_RUNTIME_METHODS=['cwrap', 'setValue', 'getValue', 'AsciiToString', 'writeStringToMemory']" \
    -o myclib.js
    person.c

我不记得为什么我们在 _get_persons 中有一个前导下划线,但这就是 Emscripten 的工作原理。

在JS中获取对象

const createModule = require('./myclib');

let myclib;
let Module;

export const myclibRuntime = createModule().then((module) => {
  get_persons: Module.cwrap('get_persons', 'number', []),
});

它的作用是创建一个 get_persons() JS 函数,它是 C get_persons() 函数的包装器。 JS函数的返回值为“数字”。 Emscripten 知道 C 中的 get_persons() 函数返回一个指针,包装器会将该指针转换为 JS 编号。 (WASM 中的指针是 32 位的。)

在JS中操作对象

const persons = get_persons();
Module.getValue(persons, 'i32');  // Returns the age of the first person
Module.AsciiToString(Module.getValue(persons + 4, 'i32'));  // Name of first person

// Set the second person to be "Alice", age 18
const second_person = persons + 8;
Module.setValue(second_person, 18, 'i32');
const buffer = Module._malloc(6);  // Length of "Alice" plus the null terminator
Module.writeStringToMemory("Alice", buffer);
Module.setValue(second_person + 4, buffer, 'i32');

尽管there seems to be an even lower level way,这是一种相当低级的方法。正如其他人所建议的那样,可能有更高级别的工具可以帮助 C++ 和 Rust。

在JS中创建对象

您可以在 JS 中使用 _malloc() 创建对象(并使用 _free() 释放它们),就像我们对上面的字符串所做的那样,然后将它们的指针传递给 C 函数。但是,正如我所说,在 C 中创建它们可能更容易。在任何情况下,任何_malloc()ed 最终都必须被释放(因此上面的字符串创建不完整)。 FinalizationRegistry 可以提供帮助。

【讨论】:

    【解决方案2】:

    是的,这是可能的。

    WebAssembly 将对象存储在 linear memory 中,这是一个模块可以读取和写入的连续字节数组。宿主环境(通常是 Web 浏览器中的 JavaScript)也可以读取和写入线性内存,允许它访问 WebAssembly 模块存储在那里的对象。

    这里有两个挑战:

    1. 如何找到 WebAssembly 模块存储对象的位置?
    2. 对象是如何编码的?

    您需要确保可以从 WebAssembly 模块和 JavaScript 主机读取和写入这些对象。

    我会选择一个已知的内存位置和一个已知的序列化格式,并使用它来从双方读取/写入。

    【讨论】:

    • 我明白了,谢谢。有没有建议的序列化格式?我听说过使用 Arrow ......这会是这里的最佳选择吗?换句话说,假设我们有一个 1GB+ 的共享数据对象,那么从两端读取数据的最快方法是什么?
    • 如果您在 Emscripten 中使用 Embind,或者在 Rust 中使用 wasm-bindgen,它们会自动为您执行此操作 - 也就是说,它们会包装您注释的任何 C++/Rust 对象并将它们公开给 JS常规对象,允许您从任一侧修改它们。
    • @colinE -- 谢谢你的回答,我已经添加了赏金,想在这里添加更多细节吗?
    • @RReverser 很酷,想展示一个非常基本的示例实现,C++ 或 rust 都可以(但如果可以选择,我更喜欢 C++)。
    • 您查看文档了吗?有很多例子。
    猜你喜欢
    • 2022-10-06
    • 2011-04-12
    • 1970-01-01
    • 1970-01-01
    • 2019-06-13
    • 2012-04-15
    • 1970-01-01
    • 1970-01-01
    • 2021-07-03
    相关资源
    最近更新 更多