在 Web Assembly 中将数组和对象从 JavaScript 传递到 C++

Dav*_*ini 2 javascript c++ webassembly

好吧,我已经有一段时间反对这个了。我更专注于 JavaScript 和 C# 开发,但我在 C++ 方面有一些经验。我的问题是

  1. 我需要找到一种简单的方法来获取 Javascript 对象并将其通过 WebAssembly 传递给 c++
  2. 我需要对 Javascript 数组做同样的事情
  3. 我可能需要对 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));
Run Code Online (Sandbox Code Playgroud)

因此,应该采用一个整数数组,将它们加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));
Run Code Online (Sandbox Code Playgroud)

测试 1 再次返回 5。测试 2 返回 0。

现在只是看看我是否可以让 c++ 返回一个数组:

//c++
int * test(){
  int arr[] = {12, 32, 54, 31};
    return arr;
}

//Javascript
console.log(wasmInstance.exports.test());
Run Code Online (Sandbox Code Playgroud)

返回-16。有点时髦,可能是由于两者之间的指针问题。现在如果我尝试这个:

//c++
int test(){
  int arr[] = {12, 32, 54, 31};
    return arr[0];
}

//Javascript
console.log(wasmInstance.exports.test());
Run Code Online (Sandbox Code Playgroud)

现在它返回 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));
Run Code Online (Sandbox Code Playgroud)

返回 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));
Run Code Online (Sandbox Code Playgroud)

这将返回未定义。

所以问题是,我是否搞砸了 c++、javascript、wasm、所有 3 种语言,还是什么?我花了三天时间在互联网上寻找答案,我唯一能找到的是声明这是可能的,但没有示例或文档说明如何做到这一点。我找到的最好的文档是 DevRant,它仍然没有给我答案。

那么这是否可能,如果可以,是否有任何我可以遵循的工作示例,或者这根本不可能?

cod*_*fin 6

对我来说,当我意识到在 c/c++ 和 JavaScript 之间来回传递数据最好用数字来完成时,我灵光一现;具体来说,指向数据的指针和有关数据的信息(堆上的大小)。来自 JavaScript 世界并使用指针/堆有点令人生畏,但我有几个例子,我认为它们是一个好的开始。

下面的代码片段显示了一个工作示例,将数字数组从 JavaScript 传递到“c++”,然后“c++”将值的总和返回给 JavaScript。我使用引号是因为 C++ 代码已使用emscripten编译到一个名为 的文件/build/sum.wasm,并且所有内容都使用一个名为 的文件“粘合”在一起,/build/sum.js可以看到该文件包含在该/index.html文件中。

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);
Run Code Online (Sandbox Code Playgroud)
<!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>
Run Code Online (Sandbox Code Playgroud)

完整的工作代码、原始 C++ 文件和带有编译命令的自述文件可以在这里找到:https: //codesandbox.io/s/cpp-javascript-web assembly-basic-example-w8r7ug /index.js 。

简而言之:

  1. 编译c++代码
em++ -o build/sum.js sum.cpp -s NO_EXIT_RUNTIME=1 -s "EXPORTED_RUNTIME_METHODS=['ccall']" -s "EXPORTED_FUNCTIONS=['_free','_malloc']" -g
Run Code Online (Sandbox Code Playgroud)
  1. 在 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;
}
Run Code Online (Sandbox Code Playgroud)
  1. 另外,在 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);
Run Code Online (Sandbox Code Playgroud)

为了回答你的问题,是的,可以将复杂的数据从 JavaScript 传递到 c/c++ 并接收回来的数据,上面有一个传递数字数组并接收返回总和的工作示例。




现在我想提供一个更复杂数据的示例,包括数组数组和对象。最后,按照下一个示例,可以更轻松地来回传递复杂数据并推理代码。即使对于上面这样的基本示例,我也建议这样做。

使用msgpack可以轻松地“在 JSON 等多种语言之间交换数据”。编译和以前一样。只需在 C++ 中添加一些关于如何msgpack在 JavaScript 文件中对数据进行编码和解码中定义数据的信息,就可以开始了。

完整的代码和自述文件可以在https://codesandbox.io/s/cpp-javascript-web assembly-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);
Run Code Online (Sandbox Code Playgroud)
<!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>
Run Code Online (Sandbox Code Playgroud)

这是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();
}
Run Code Online (Sandbox Code Playgroud)

  • 感谢您的回答!它帮助我揭开了这个概念的神秘面纱。但是,在线:`Module[type.heap].set(typedArray, heapPointer &gt;&gt; 2);`,为什么使用按位移位运算符? (2认同)