Webassembly:可能有共享对象吗?
Webassembly: possible to have shared objects?
我想知道使用 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
对象。这怎么可能呢?或者设置是否需要像 'master-slave' 那样,一个人只需要将信息传回另一个人,而主人会执行所有相关操作?我希望有一种方法可以对 webassembly 中的共享数据对象进行 CRUD,我们将不胜感激任何帮助。
作为参考:https://rustwasm.github.io/wasm-bindgen/contributing/design/js-objects-in-rust.html
是的,这是可能的。
WebAssembly 将对象存储在 linear memory 中,这是一个模块可以读取和写入的连续字节数组。主机环境(通常 JavaScript 在网络浏览器中)也可以读写线性内存,允许它访问 WebAssembly 模块存储在那里的对象。
这里有两个挑战:
- 如何找到 WebAssembly 模块存储对象的位置?
- 对象是如何编码的?
您需要确保可以从 WebAssembly 模块和 JavaScript 主机读取和写入这些对象。
我会选择一个已知的内存位置和一个已知的序列化格式,并将其用于 read/write 从双方。
正在创建对象
让我们在 C 中创建对象并 return 它:
typedef struct Person {
int age;
char* name;
} Person;
Person *get_persons(void) {
Person* sharedPersons[100];
return sharedPersons;
}
你也可以用JS创建对象,但是比较难。我稍后再讲这个。
为了让 JS 获取对象,我们定义了一个函数 (get_persons
) returns (a pointer to) 它。在本例中它是一个数组,但当然它也可以是单个对象。问题是,必须有一个函数将从 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函数的return值为“number”。 Emscripten 知道 C get_persons()
函数 return 是一个指针,包装器会将那个指针转换为 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 可以提供帮助。
我想知道使用 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
对象。这怎么可能呢?或者设置是否需要像 'master-slave' 那样,一个人只需要将信息传回另一个人,而主人会执行所有相关操作?我希望有一种方法可以对 webassembly 中的共享数据对象进行 CRUD,我们将不胜感激任何帮助。
作为参考:https://rustwasm.github.io/wasm-bindgen/contributing/design/js-objects-in-rust.html
是的,这是可能的。
WebAssembly 将对象存储在 linear memory 中,这是一个模块可以读取和写入的连续字节数组。主机环境(通常 JavaScript 在网络浏览器中)也可以读写线性内存,允许它访问 WebAssembly 模块存储在那里的对象。
这里有两个挑战:
- 如何找到 WebAssembly 模块存储对象的位置?
- 对象是如何编码的?
您需要确保可以从 WebAssembly 模块和 JavaScript 主机读取和写入这些对象。
我会选择一个已知的内存位置和一个已知的序列化格式,并将其用于 read/write 从双方。
正在创建对象
让我们在 C 中创建对象并 return 它:
typedef struct Person {
int age;
char* name;
} Person;
Person *get_persons(void) {
Person* sharedPersons[100];
return sharedPersons;
}
你也可以用JS创建对象,但是比较难。我稍后再讲这个。
为了让 JS 获取对象,我们定义了一个函数 (get_persons
) returns (a pointer to) 它。在本例中它是一个数组,但当然它也可以是单个对象。问题是,必须有一个函数将从 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函数的return值为“number”。 Emscripten 知道 C get_persons()
函数 return 是一个指针,包装器会将那个指针转换为 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 可以提供帮助。