How to handle passing/returning array pointers to emscripten compiled code?

The expected format of Module.cwrap does allow for ‘array’s to be passed into the function, but will assert on result and fail if you attempt to return an array

displayArrayA=Module.cwrap('displayArray','array',['array'])
displayArrayA([1,2,3]) 
// Assertion failed: ccallFunc, fromC assert(type != 'array')

A second restriction of this is that incoming arrays are expected to be byte arrays, meaning you would need to convert any incoming double arrays into unsigned 8-bit numbers

displayArrayA=Module.cwrap('displayArray','number',['array'])
displayArrayA(new Uint8Array(new Float64Array([1,2,3]).buffer))

Calling the method this way will invoke your function, temporarily copying your arrays to the Emscripten stack which will be reset after your invoked function’s execution, making the returned Array offset potentially unusable as it is freed stackspace.

It is much more preferable, if you want the results of your function, to allocate and preserve an array inside Emscriptens Heap system.

Emscripten code is only able to access memory that has been allocated within Emscripten’s Heap space. The arrays that you are attempting to pass into the function are being allocated outside the heap that the Emscripten code is running against, and do not match the raw pointer type expected in the incoming arguments.

There are several ways that you can gain access to an array to pass data to your functions. All of these require Emscripen having knowledge of the location of your memory inside the emscripten Module.HEAP*, so the initial step is at some point to call the Emscripten “_malloc” function.

var offset = Module._malloc(24)

This would allow you to allocate the required 24 bytes in the Emscripten heap needed for your 3x 8-byte double array, and returns a Number offset in the Emscripten heap denoting the U8 TypedArray offset reserved for your array. This offset is your pointer, and will automatically work being passed into your cwrap displayArray function when it is configured to use the raw pointer offsets.

displayArray=Module.cwrap('displayArray','number',['number'])

At this point, if you wish to access or modify the contents of the array, as long as the malloc is valid, you have at least the following options:

  1. Set the memory using a temporarily wrapped Float64 array, with no easy way to recover the value except the following 2 methods of access

    Module.HEAPF64.set(new Float64Array([1,2,3]), offset/8);
    displayArray(offset);
    
  2. Module.setValue will use the ‘double’ hint to automatically modify the HEAPF64 offset, divided by 8.

    Module.setValue(offset, 1, 'double')
    Module.setValue(offset+8, 2, 'double')
    Module.setValue(offset+16, 3, 'double')
    displayArray(offset)
    var result = [];
    result[0] = Module.getValue(offset,'double'); //98
    result[1] = Module.getValue(offset+8,'double') //99
    result[2] = Module.getValue(offset+16,'double') //100
    
  3. If you wish to use your pointer more extensively on the Javascript side, you can pull a subarray TypedArray off the HEAPF64 entry manually. This allows you to easily read the values once you have finished executing your function. This TypedArray is backed by the same heap as the rest of the Emscripten, so all changes performed on the Javascript side will be reflected on the Emscripten side and vice-versa:

    var doublePtr = Module.HEAPF64.subarray(offset/8, offset/8 + 3);
    doublePtr[0] = 1;
    doublePtr[1] = 2;
    doublePtr[2] = 3;
    // Although we have access directly to the HEAPF64 of the pointer,
    // we still refer to it by the pointer's byte offset when calling the function
    displayArray(offset);
    //doublePtr[] now contains the 98,99,100 values
    

Leave a Comment