Search code examples
c++arraystypescriptwebassemblyemscripten

Emscripten handover double array - Apparently random address is received


I have trouble handing over a double array to a WASM file, that is generated via emscripten. I have generated it as a .js output file with WASM=1, so I also have a wasm file.

This is my abbreviated C++ code:

#include <iostream>
using namespace std;

extern "C" {

  int main(int argc, char** argv) {
    return 0;
  }

  double *calculate(double *x, double *y, int array_length) {
    std:cout << "Array Length: " << array_length << endl;
    for (int i = 0; i < array_length; i++) {
      std::cout << "Coords: " << x[i] << ", " << y[i] << endl;
    }
    return new double[7]{1,1,2,3,5,8,13};
  }
}

It produces this output in the browser console:

Array Length: 4 average.js:8:16057
Coords: 2.36377e+232, 2.36377e+232 average.js:8:16057
Coords: 5.9419e-310, 5.9419e-310 average.js:8:16057
Coords: 4.28375e-319, 4.28375e-319 average.js:8:16057
Coords: 1.4854e-313, 1.4854e-313

I can see it does something.

This is the code calling it:

import { CalculationAdapterInterface, Coords, GraphData } from "../CalculationAdapterInterface";
import averageModule from "./average"

export default class CalculationWasmAdapter implements CalculationAdapterInterface {
  

  async calculate(data: GraphData): Promise<Coords|void> {
    
    const x = [ 5.0, 3.0, 2.0, 5.0];
    const y = x.map(x => x/2);
    const to_byte = (arr: number[]) => { return new Float64Array(arr).buffer}

    const filePath = window.location.origin + "/average.wasm";
    averageModule({locateFile: () => filePath}).then((module: any) => {
      const calculate = module.cwrap('calculate', 'number', ['array', 'array', 'int'])
      calculate(to_byte(x), to_byte(y), x.length);
    })
  }
}

I don't understand why the output in the browser is generated. When I change the input values in the array, it does not change the printed values (except for how many are printed). I strongly suspect that the c++ code does not receive the array and reads from some default allocated space. But why?


Solution

  • I've only ever done this once and I don't claim to be the expert but I had success passing an array of doubles from JavaScript to C++ using this technique.

    The way I understand it, if you want to access memory from JavaScript, you have to be aware that JavaScript and WebAssembly have different memory layouts. You use HEAPF64.set to copy data from a JavaScript array (with its own memory layout) to the Emscripten HEAP, where it can be accessed by the WebAssembly module.

    Here's my example, which is node.js (sorry, that's how I was using it), but something similar should be possible in the browser platform.

    const factory = require('./src/passarray.js');
    factory().then(instance => {
      const doubleArray = new Float64Array([1.0, 2.0, 3.0, 4.0, 5.0]);
    
      const arrayPointer = instance._malloc(doubleArray.length * doubleArray.BYTES_PER_ELEMENT);
      instance.HEAPF64.set(doubleArray, arrayPointer / doubleArray.BYTES_PER_ELEMENT);
    
      instance._exampleFunction(arrayPointer);
    
      instance._free(arrayPointer);
    });
    

    On the C++ side:

    extern "C" {
      EMSCRIPTEN_KEEPALIVE
      void exampleFunction(double* elems) { 
        // [your code here]
      }
    }