user1940163
user1940163

Reputation: 191

How to pass array of strings between javascript and C/C++ code with webassembly/emscripten?

I am trying to write a web application that will do sort of word processing (say spell check, grammar check, word analysis) using back-end C/C++ code. (I have got c/C++ code working in another desktop app... I want to bring it to web). I want an example minimal code doing this (pass array of strings from JavaScript to c/c++ code...c/c++ code will do the word operations... I have this code ......and the resulting array of strings will be sent back to JavaScript where they will be processed further. (passing arrays to and from is important) Please point me to any such code/tutorial, from where I can make a start.

I searched GitHub. I found several projects using emscripten but could not get this anywhere. (Only place I could get some clue was Hunspell built with emscripten ... however I could not build it successfully)

Please let me know . Thanks in advance.

Upvotes: 5

Views: 1068

Answers (1)

Nikolay Handzhiyski
Nikolay Handzhiyski

Reputation: 1579

First prepare the C++ side to receive a string (character array):

static char *string_buffer = NULL;
static size_t string_length = 0;

void EMSCRIPTEN_KEEPALIVE string_start_js(void) {}
void EMSCRIPTEN_KEEPALIVE string_final_js(void) {}

char * EMSCRIPTEN_KEEPALIVE string_ensure(size_t length) 
{ 
  // ensure that the buffer is long enough
  if (length <= string_length) return string_buffer;

  // grow the buffer
  char *new_buffer = realloc(string_buffer, length + 1);

  // handle the out of memory
  if (new_buffer == null) return NULL;

  // remember
  string_buffer = new_buffer;
  string_length = length;

  // done
  return string_buffer;
}

void EMSCRIPTEN_KEEPALIVE string_handle(size_t length)
{
  // sanity
  if (string_buffer == NULL || length > string_length) halt;

  // terminate
  string_buffer[length] = 0;

  // work with the string characters, store/process it 
}

void EMSCRIPTEN_KEEPALIVE string_clear(void)
{
  // friendly
  if (string_buffer == NULL) return;

  // free
  free(string_buffer);

  // remember
  string_buffer = NULL;
  string_length = 0;
}

From the JavaScript side send one string to the C++ side:

let strings = ["abc", "defg", "1"];

// inform the C++ side that some strings are going to be transferred
exports['string_start_js']();

// send all strings
for (var i = 0; i < strings.length; i++) 
{
  // single string to transport
  let string = strings[i];

  // convert to a byte array
  let string_bytes = new TextEncoder().encode(string);

  // ensure enough memory in the C++ side
  let string_offset = exports["string_ensure"](string_bytes.byteLength);

  // handle the out of memory 
  if (string_offset == 0) throw "ops...";

  // have view of the instance memory
  let view = new Uint8Array(memory.buffer, string_offset, string_bytes.byteLength);

  // copy the string bytes to the memory
  view.set(string_bytes);

  // handle 
  exports['string_handle'](string_bytes.byteLength);
}

// inform the C++ side that all strings were transferred 
exports['string_final_js']();

// clear the used buffer
exports['string_clear']();

The way from C++ to WASM can be more simple:

  • have a character array (pointer) and its length
  • call an import function to give the array pointer to JavaScript and its length
  • make a view of the memory
  • read the characters from the view

Something like this in the C++ side:

extern "C" {
  extern void string_start_cpp(void);
  extern void string_final_cpp(void);
  extern void string_fetch(char *pointer, size_t length);
}

void foo(void)
{
  // inform the JavaScript side that 
  string_start_cpp();

  // runtime string
  const char *demo = "abc";

  // send to JavaScript
  string_fetch(demo, strlen(demo));

  // inform the JavaScript side all strings were send
  string_final_cpp();
}

And in JavaScript supply the functions during the instance creation:

string_start_cpp: function(offset, length) 
{
  console.log("{");
},
string_final_cpp: function(offset, length) 
{
  console.log("}");
},
string_fetch: function(offset, length) 
{
  // view the bytes
  let view = new Uint8Array(memory.buffer, offset, length); 

  // convert the UTF-8 bytes to a string
  let string = new TextDecoder().decode(view);

  // use
  console.log(string);
}

I did not test the code, there could be some syntax errors. You can improve in many places the code, but the idea is what counts.

Upvotes: 2

Related Questions