3

I have a simple C function that modifies elements of an integer array. I can convert it to JavaScript using Emscripten (emcc) without problems. But when I call the function on a JS array, the values in it do not seem to change. Please help.

This is the C function definition:

/* modify_array.c */
void modify_array(int X[8]) {
  int i;
  for (i = 0; i < 8; ++i) {
    X[i] += 1;
  }
}

This is the command I used to transpile the C code to JS:

emcc modify_array.c -o modify_array.js -s EXPORTED_FUNCTIONS="['_modify_array']"

And this is the JavaScript (Node.js) code for invoking the transpiled JS code:

var mod = require("./modify_array.js");
var f = mod.cwrap("modify_array", "undefined", ["array"]);

var X = [0, 1, 2, 3, 4, 5, 6, 7];
var bytesX = new Uint8Array(new Int32Array(X).buffer);

/* Invoke the emscripten-transpiled function */
f(bytesX);


console.log(new Int32Array(bytesX.buffer));

After running the JS code, the buffer contains values that are identical to the original values, not the incremented values. Why? How can I get the updated values?

Shanqing Cai
  • 3,756
  • 3
  • 23
  • 36
  • Shouldn't you pass a pointer and an array size in C instead? – zerkms Jan 28 '15 at 21:35
  • @zerkms: I tried the C function signature modify_array(int *X, int size), then did the emscripten workflow again, with modifications to the JavaScript syntax of course. The behavior appears to be exactly the same as the original array-based C function signature. – Shanqing Cai Jan 28 '15 at 21:43
  • Could you show `modify_array.js` contents? – zerkms Jan 28 '15 at 21:44
  • @zerkms, Thanks for the suggestion. But I don't see how that might be helpful. The size of the generated file is pretty large (9.8k lines) and the code is not very human-readable. If you want, I can PM it to you (if there is such a thing on StackOverflow). – Shanqing Cai Jan 28 '15 at 21:46
  • So the `modify_array` function that is originally 3 lines of C code translated to 9.8k LOC? – zerkms Jan 28 '15 at 22:14
  • 1
    That is correct, @zerkms. This is apparently how Emscripten works... After a little more searching on StackOverflow (which I probably should have done beforehand), I found this Q+A: http://stackoverflow.com/questions/17883799/how-to-handle-passing-returning-array-pointers-to-emscripten-compiled-code. This solves my problem. Using Module._malloc to create a chunk of heap memory, specifying the JS argument as "number" and passing it to the JS function does the trick. Later you can use "getValue" or "subArray" to retrieve the new values in the same heap memory location. – Shanqing Cai Jan 28 '15 at 22:21
  • @scai, it sounds like you figured it out correctly. C++ is translated into JS with all memory done virtually in arrays. As such a normal JS object won't make sense to C++ code, but base types are translated reasonably (usually). – Charles Ofria Jan 28 '15 at 23:24

1 Answers1

9

Emscripten's memory model is a single flat array. That means that when you provide an array of data to a compiled C method, it is copied into the single array, which is the only place it can be accessed (the ccall/cwrap methods do this for you). In other words, all arguments you pass are by value, not by reference, even if they are arrays (which in JS normally are passed by reference).

To work within the emscripten memory model, you can use memory in the single flat array,

var ptr = Module._malloc(8);
var view = Module.HEAPU8.subarray(ptr, ptr+8);
var f = Module.cwrap("modify_array", "undefined", ["number"]);
f(ptr);

That reserves a space in the single array, and using a subarray on the single array, we can access its values. Note the use of number as the type. We are passing ptr, which is a pointer to the buffer. As a pointer, it is just a number referring to a location in the single array.

(Note that you should call free to release the memory at the right time.)

zwcloud
  • 4,546
  • 3
  • 40
  • 69
Alon Zakai
  • 1,038
  • 5
  • 4