好吧,我已经对此进行了一段时间的抨击。我更喜欢JavaScript和C#,但我在C++方面有一些经验。我的问题是
- 我需要找到一种简单的方法来获取Javascript对象,并通过WebAssembly将其传递给c++
- 我需要对Javascript数组执行同样的操作
- 我可能需要对Javascript对象的Javascript数组执行同样的操作
所以从我在一个简单数组上尝试的开始:
//c++
int testArr(int * arrIn, int length){
int results = 0;
for(int i = 0; i < length; i++){
results += arrIn[i] + 1;
}
return results;
}
//javascript
let arr = [20, 50, 90, 62, 98];
console.log(wasmInstance.exports.testArr(arr, arr.length));
因此,应该取一个整数数组,将它们加1(基本上是为了测试循环(。它返回5。我预计它会返回325。因此,查看类型化数组是下一个合乎逻辑的步骤。。。
//c++
int testArr(int * arrIn, int length){
int results = 0;
for(int i = 0; i < length; i++){
results += arrIn[i] + 1;
}
return results;
}
//javascript
let arr = [20, 50, 90, 62, 98];
let typedArray = new Int32Array(arr);
//test 1
console.log(wasmInstance.exports.testArr(typedArray, arr.length));
//test 2
console.log(wasmInstance.exports.testArr(typedArray.buffer, arr.length));
测试1再次返回5。测试2返回0。
现在看看我是否可以用c++返回一个数组:
//c++
int * test(){
int arr[] = {12, 32, 54, 31};
return arr;
}
//Javascript
console.log(wasmInstance.exports.test());
返回-16。有点古怪,可能是因为两者之间的指针问题。现在,如果我尝试这个:
//c++
int test(){
int arr[] = {12, 32, 54, 31};
return arr[0];
}
//Javascript
console.log(wasmInstance.exports.test());
现在它返回12。
到目前为止,这是我在传递数组方面所做的工作,在大多数情况下,这似乎是不可能的。现在,传递对象。上帝保佑我。请善待c++,因为它不是我最擅长的语言。
//c++
class Vector3{
public:
float x;
float y;
float z;
Vector3(float X, float Y, float Z){
x = X;
y = Y;
z = Z;
}
};
int test(Vector3 position){
return position.x;
}
//javascript
let position = {x: 21, y: 30, z: 12};
console.log(wasmInstance.exports.test(position));
这将返回0而不是21;
现在,对于邪恶的三位一体,一组javascript对象。。。
//c++
class Vector3{
public:
float x;
float y;
float z;
Vector3(float X, float Y, float Z){
x = X;
y = Y;
z = Z;
}
};
Vector3 test(Vector3 positions[], int length){
return positions[0];
}
//javascript
let positions = [{x: 21, y: 30, z: 12},{x:15, y: 24, z: 14}]
console.log(wasmInstance.exports.test(positions, positions.length));
这将返回undefined。
所以问题是,我是不是在c++、javascript、wasm、all 3中搞砸了?我花了3天时间在互联网上搜索答案,我唯一能找到的就是声明这是可能的,没有例子或文件说明如何做到这一点。我找到的最好的文档是DevRant,它仍然没有给我答案。
那么这是可能的吗?如果是的话,我有什么可以效仿的例子吗?或者这根本不可能吗?
对我来说,当我意识到在c/c++和JavaScript之间来回传递数据最好使用数字时,我感到非常兴奋;具体地说,指针指向数据和有关数据的信息(堆上的大小(。来自JavaScript世界,使用指针/堆有点吓人,但我有几个例子,我认为这是一个良好的开端。
下面的片段显示了将一个数字数组从JavaScript传递到"0"的工作示例;c++";以及";c++";将值的总和返回给JavaScript。我使用引号是因为c++代码是使用emscripten编译到一个名为CCD_ 1的文件中的;胶合";一起使用称为CCD_ 2的文件,该文件可以被视为包括在CCD_。
const MAX_TRIES = 10;
let numTries = 0;
const moduleInterval = setInterval(() => {
if (!Module) {
numTries++;
}
if (numTries >= MAX_TRIES) {
clearInterval(moduleInterval);
}
// Wait for "runtime initialization"
// Module is defined in build/sum.js
if (Module && Module.calledRun) {
clearInterval(moduleInterval);
const TYPES = {
i8: { array: Int8Array, heap: "HEAP8" },
i16: { array: Int16Array, heap: "HEAP16" },
i32: { array: Int32Array, heap: "HEAP32" },
f32: { array: Float32Array, heap: "HEAPF32" },
f64: { array: Float64Array, heap: "HEAPF64" },
u8: { array: Uint8Array, heap: "HEAPU8" },
u16: { array: Uint16Array, heap: "HEAPU16" },
u32: { array: Uint32Array, heap: "HEAPU32" }
};
function transferNumberArrayToHeap(array, type) {
const typedArray = type.array.from(array);
const heapPointer = Module._malloc(
typedArray.length * typedArray.BYTES_PER_ELEMENT
);
Module[type.heap].set(typedArray, heapPointer >> 2);
return heapPointer;
}
function containsFloatValue(array) {
return array.some((value) => !Number.isInteger(value));
}
function sumArrayValues(array) {
const hasFloatValue = containsFloatValue(array);
let pointerToArrayOnHeap;
try {
pointerToArrayOnHeap = transferNumberArrayToHeap(
array,
hasFloatValue ? TYPES.f32 : TYPES.i32
);
return Module.ccall(
hasFloatValue ? "sumFloatArray" : "sumIntArray", // The name of C++ function
"number", // The return type
["number", "number"], // The argument types
[pointerToArrayOnHeap, array.length] // The arguments
);
// Note: The method can also be called directly
// return Module[hasFloatValue ? '_sumFloatArray' : '_sumIntArray'](arrayOnHeap, array.length);
} finally {
Module._free(pointerToArrayOnHeap);
}
}
const sumInt = sumArrayValues([20, 50, 90, 62, 98]);
const sumFloat = sumArrayValues([20, 50, 90, 62, 98, 0.5]);
const outputElement = document.querySelector("#output");
outputElement.innerHTML = `Sum of integers: <strong>${sumInt}</strong>
<br>
Sum of floats: <strong>${sumFloat}</strong>`;
}
}, 10);
<!DOCTYPE html>
<html lang="en-us">
<head>
<meta charset="utf-8" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Sum</title>
</head>
<body>
<div id="output"></div>
<!-- /build/sum.js is a file built using emscripten -->
<script src="https://w8r7ug.csb.app/build/sum.js"></script>
</body>
</html>
完整的工作代码、原始c++文件和带有编译命令的README可以在这里找到:https://codesandbox.io/s/cpp-javascript-webassembly-basic-example-w8r7ug/index.js.
简而言之:
- 编译c++代码
em++ -o build/sum.js sum.cpp -s NO_EXIT_RUNTIME=1 -s "EXPORTED_RUNTIME_METHODS=['ccall']" -s "EXPORTED_FUNCTIONS=['_free','_malloc']" -g
- 在JavaScript文件中,使用以下方法将要发送到堆的数据传输:
function transferNumberArrayToHeap(array, type) {
const typedArray = type.array.from(array);
const heapPointer = Module._malloc(
typedArray.length * typedArray.BYTES_PER_ELEMENT
);
Module[type.heap].set(typedArray, heapPointer >> 2);
return heapPointer;
}
- 此外,在JavaScript文件中调用已编译的c++函数并传递有关数据的信息(指针和大小(
Module.ccall(
hasFloatValue ? "sumFloatArray" : "sumIntArray", // The name of C++ function
"number", // The return type
["number", "number"], // The argument types
[pointerToArrayOnHeap, array.length] // The arguments
);
// Or call the method directly
Module[hasFloatValue ? '_sumFloatArray' : '_sumIntArray'](pointerToArrayOnHeap, array.length);
要回答您的问题,是,可以将复杂数据从JavaScript传递到c/c++并接收回数据。以上是一个传递数字数组并接收回总和的工作示例。
现在我想提供一个更复杂的数据示例,包括数组和对象的数组。最后,通过下面的示例,可以更容易地来回传递复杂的数据并对代码进行推理。这是我建议这样做的方式,即使是像上面这样的基本示例。
使用msgpack可以很容易地";在JSON等多种语言之间交换数据"编译和以前一样。只要在c++中添加一点关于msgpack
中如何定义数据的信息,就可以对JavaScript文件中的数据进行编码和解码。
完整的代码和自述文件可以在https://codesandbox.io/s/cpp-javascript-webassembly-msgpack-example-wh8bwy?file=/index.js.下面的代码段也将运行代码。
const MAX_TRIES = 10;
let numTries = 0;
const moduleInterval = setInterval(() => {
if (!Module) {
numTries++;
}
if (numTries >= MAX_TRIES) {
clearInterval(moduleInterval);
}
// Wait for "runtime initialization"
// Module is defined in build/vector3.js
if (Module && Module.calledRun && msgpack5) {
clearInterval(moduleInterval);
const msgpack = msgpack5();
const encodedPositions = msgpack.encode([{
positionId: "position1",
x: 21,
y: 30,
z: 12
},
{
positionId: "position2",
x: 15,
y: 24,
z: 14
}
]);
let inputPointer = Module._malloc(encodedPositions.length);
Module.HEAP8.set(
encodedPositions,
inputPointer / encodedPositions.BYTES_PER_ELEMENT
);
const outputPointer = Module._malloc(8);
const processedVector3IdsPointer = Module.ccall(
"processVector3",
"number", ["number", "number", "number"], [inputPointer, encodedPositions.length, outputPointer]
);
// OUTPUT
let offset = Module.getValue(outputPointer, "i64");
const processedVector3IdsData = new Uint8Array(
Module.HEAPU8.subarray(
processedVector3IdsPointer,
processedVector3IdsPointer + offset
)
);
const processedVector3Ids = msgpack.decode(processedVector3IdsData);
console.log("Successfully Processed ids: ", processedVector3Ids);
Module._free(inputPointer);
Module._free(outputPointer);
Module._free(processedVector3IdsPointer);
}
}, 10);
<!DOCTYPE html>
<html lang="en-us">
<head>
<meta charset="utf-8" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Vector3</title>
</head>
<body>
<p>See console</p>
<script src="https://cdnjs.cloudflare.com/ajax/libs/msgpack5/6.0.0/msgpack5.min.js"></script>
<!-- /build/vector3.js is a file built using emscripten -->
<script src="https://wh8bwy.csb.app/build/vector3.js"></script>
</body>
</html>
以下是vector3.cpp
,供快速参考。
#include <emscripten.h>
#include <msgpack.hpp>
#include <iostream>
struct Position {
public:
MSGPACK_DEFINE_MAP(positionId, x, y, z);
std::string positionId;
float x;
float y;
float z;
};
class Vector3 {
public:
float x;
float y;
float z;
Vector3(float X, float Y, float Z){
x = X;
y = Y;
z = Z;
}
};
EMSCRIPTEN_KEEPALIVE
extern "C" char* processVector3(char* inputPointer, int inputSize, char* outputPointer) {
msgpack::object_handle objectHandle = msgpack::unpack(inputPointer, inputSize);
msgpack::object object = objectHandle.get();
std::vector<Position> positions;
object.convert(positions);
std::vector<std::string> positionIds;
for (auto& position : positions) {
// CREATE VECTOR3 OBJECTS AND DO WORK
try {
std::cout << "Attempting to process " << position.positionId << ": " << position.x << " " << position.y << " " << position.z << std::endl;
Vector3 vector3(position.x, position.y, position.z);
positionIds.push_back(position.positionId);
std::cout << "Successfully processed " << position.positionId << ": " << vector3.x << " " << vector3.y << " " << vector3.z << std::endl;
} catch (std::exception& e) {
std::cout << e.what() << std::endl;
}
}
// OUTPUT
msgpack::sbuffer sbuf;
msgpack::pack(sbuf, positionIds);
*outputPointer = sbuf.size();
return sbuf.data();
}