Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing a JavaScript array of strings to a C function with Emscripten

I'm new to WebAssembly and Emscripten and am trying to pass an array of strings in JavaScript to a C function for further processing with Module.cwrap(...). Ideally I'd also like to return an array of strings back to JavaScript from C.

Below is some pseudocode of what I'm looking for:

JS

const strings = ["foo", "bar", "fool", "gnar"]
const result = Module.cwrap("myCFunc", "array", ["array"])
console.log(result) // ["my", "transformed", "array"]

C

char **myCFunc(char **input) {
    // do some processing. Specifically some md5 hashing...
    return output;
}

My guess is I won't be able to pass multi-dimensional arrays by themselves from JS to C functions, but will have to use WebAssembly heap memory instead. I know that the emscripten JS API has support for this kind of thing, but I haven't written C in ages and the specifics of this type of pointer manipulation is beyond me at the moment.

like image 309
Brannon Avatar asked Oct 15 '17 01:10

Brannon


1 Answers

You have 2 questions here: one is how to pass an array of data into a C function, and the other is how to get an array of data back from a C function.

For the first question, you need to convert each JS string to a C string, allocate C memory to for the array holding each such C string, and write the values to the array-- then you can pass that to your function.

For example:

emcc flags:

-s EXPORTED_FUNCTIONS="['UTF8ToString','stringToUTF8Array','lengthBytesUTF8']"

-s EXTRA_EXPORTED_RUNTIME_METHODS="['_free','_malloc','setValue']"

Javascript:

// convert a Javascript string to a C string
function str2C(s) {
  var size = lengthBytesUTF8(s) + 1;
  var ret = _malloc(size);
  stringToUTF8Array(s, HEAP8, ret, size);
  return ret;
}

function run_with_strings(strings) {
  let c_strings = strings.map(x => str2C(x));


  // allocate and populate the array. adapted from https://stackoverflow.com/a/23917034
  let c_arr = _malloc(c_strings.length*4); // 4-bytes per pointer
  c_strings.forEach(function(x, i) {
    Module.setValue(argsC + i * 4, x, "i32");
  });

  // invoke our C function
  let rc = myCFunc(c_strings.length, c_arr);

  // free c_strings
  for(let i = 0; i < c_strings.length; i++)
    _free(c_strings[i]);

  // free c_arr
  _free(c_arr);

  // return
  return rc;
}

For the second question, your C function as is might be difficult to use because it does not return the number of items (though I supposed you could infer that if the last item is NULL and no others are NULL). One solution would be for your function to return an opaque pointer that could then be fed back into some helper functions, e.g.:

in C:

struct myCFuncReturnValue {
  char **values;
  unsigned int count;
};

struct myCFuncReturnValue *myCFunc(char **input) {
  struct myCFuncReturnValue *p = calloc(1, sizeof(*p));
  ...
  p->count = ...;
  p->values = ...;
  return p;
}

char *myCFunc_value(struct myCFuncReturnValue *p, unsigned int i) {
  return i < p->count ? p->values[i] : NULL;
}

unsigned int myCFunc_count(struct myCFuncReturnValue *p) {
  return p->count;
}

void myCFunc_destroy(struct myCFuncReturnValue *p) {
  // maybe here you need to free each p->values[i];
  free(p->values);
  free(p);
}

Javascript:

// continuing with the rc value from run_with_strings():

for(let i = 0, j = myCFunc_count(rc); i < j; i++) {
  let c_string = myCFunc_value(rc, i);
  console.log(UTF8ToString(c_string));
}

myCFunc_destroy(rc);
like image 73
mwag Avatar answered Nov 04 '22 06:11

mwag