loading...
Microsoft Azure

Experimenting with Web Assembly and Node.js

nebrius profile image Bryan Hughes Updated on ・4 min read

Adventures in Web Assembly (4 Part Series)

1) Experimenting with Web Assembly and Node.js 2) Embedding emscripten in a Node.js library 3) Passing strings from C++ to JavaScript in Web Assembly 4) Passing structured data between C++ and JavaScript in Web Assembly

I've been wanting to play around with Web Assembly for a while, and finally had a chance to. Since I'm a mostly Node.js developer, I wanted to play around with it here instead of in the browser. There's not a lof of documentation on Web Assembly though, and the APIs are changing rapidly. I finally got it to work though, and here's how I did it.

I mostly followed the emscripten Getting Started guide, and tweaked it to fit both Node.js and to account for parts of this doc that are out of date.

Installing emscripten

The first step is to install emscripten, which is well documented on emscripten's website. I installed it inside of Windows Subsystems for Linux running Ubuntu, so YMMV if you're using a different OS.

The first step is to install some dependencies. This takes a while, so I recommend brewing a nice cup of tea during this time. I chose a nice Silver Needle this time around :)

# Install Node.js using NodeSource's Ubuntu PPA, if not already installed
curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -

# Install dependencies, as defined in emscripten's getting started guide
sudo apt-get install python2.7 nodejs cmake default-jre

Then we're ready to install emscripten itself. This also takes some time, so enjoy some more tea.

git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh

I ran into a bit of an issue with this approach. I don't know if I made a mistake, or things are just locked in. Either way, emscripten installed version 8.x.x of Node.js on it's own and overrode my 10.x.x install of Node.js. Since I'm using async/await for my Node.js code (which requires Node 10 in practice, if not in theory), I needed to manually override this by editing ~/.emscripten so the following line matches the code below:

NODE_JS = '/usr/bin/node' # This line used to have a super long path to the emsdk directory

I never ran into any problems swapping out the version of Node.js that emscripten expected, but as always, YMMV.

Creating the code

First, I created a very simple C file, courtesy of an older blog post at Dynamsoft, named test.c. (Note: the instructions in this blog post no longer work).

int add(int a, int b) {
  return a + b;
}

Then, I created the JavaScript file that consumes this C module:

const fs = require('fs').promises;
const util = require('util');

async function run() {
  async function createWebAssembly(bytes) {
    const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 });
    const env = {
      abortStackOverflow: (err) => { throw new Error(`overflow: ${err}`); },
      table: new WebAssembly.Table({ initial: 0, maximum: 0, element: 'anyfunc' }),
      __table_base: 0,
      memory,
      __memory_base: 1024,
      STACKTOP: 0,
      STACK_MAX: memory.buffer.byteLength,
    };
    return WebAssembly.instantiate(bytes, { env });
  }

  const result = await createWebAssembly(new Uint8Array(await fs.readFile('./output.wasm')));
  console.log(util.inspect(result, true, 0));
  console.log(result.instance.exports._add(9, 9));
}
run();

A few things to note about this JavaScript code. First, the code is a masheup of the code from the Dynamsoft blog post and the emscripten getting started guide. I recommend reading the emscripten getting started guide for an in-depth explanation of what's going on here. The next thing to note is the __table_base and __memory_base entries. These are named tableBase and memoryBase in the emscripten documentation and in almost all other documentation I've found, but crashes when run. I'm guessing the API changed very recently. Hat tip to this gist for pointing me in the right direction.

Running the code

Now that we've written our files, it's time to run the code. First we compile this code with the following command:

emcc -s WASM=1 -s ONLY_MY_CODE=1 -s EXPORTED_FUNCTIONS="['_add']" -o output.js *.c

This command will generate the file output.wasm that's referenced in the JavaScript code. Now we're ready to run our code!

> node index.js
{ instance: Instance [WebAssembly.Instance] {},
  module: Module [WebAssembly.Module] {} }
18

And it works! We can indeed add 9 and 9 together to get 18, calling from JavaScript to C++ and back again, all using Web Assembly!

My next step is to take the C++ messaging stack from my LED synchronization and animation project, wrap it in Web Assembly so Node.js can talk to my boards, and integrate it with Azure IoT Edge so I can control my LEDs from the cloud!

Adventures in Web Assembly (4 Part Series)

1) Experimenting with Web Assembly and Node.js 2) Embedding emscripten in a Node.js library 3) Passing strings from C++ to JavaScript in Web Assembly 4) Passing structured data between C++ and JavaScript in Web Assembly

Posted on Mar 12 '19 by:

nebrius profile

Bryan Hughes

@nebrius

Bryan Hughes is a long-time member of the Node.js and NodeBots communities, and tech activist.

Microsoft Azure

Any language. Any platform.

Discussion

markdown guide
 

Do I need the emscripten glue code?

I am seeing errors like:
LinkError: WebAssembly.instantiate(): Import #1 module="env" function="_emscripten_get_heap_size" error: function import requires a callable

 

Hey! I am experiencing the same issue. I have used a 'c' code.
Do you know the solution to this issue?

 

Use emscripten and include the JavaScript gluecode and you won't have this issue.

 

Just to make sure I'm reading this correctly (the timestamps aren't totally clear to me), did your comment on my other post indicate you figured this out?

 

Correct, yes your other post helped me figure this part out.

I feel like I am 90% there with my experiment.

 

I also learned that if you're compiling C++ code with em++ instead of emcc, make sure to include extern "C" in front of any functions in your C++ code that you want to access from JavaScript. Otherwise, you'll get errors that the function can't be found (cause of C++'s complicated name mangling rules).

 

Is this why my exported functions are found in c but when I switch to cpp, my functions are undefined. I just refactored in rage for hours.

 

I bet that's it...if for no other reason because I was also refactoring in rage for some time too before I figured it out!

Oh man you are absolutely right, I am so happy! that is one of my many questions solved. seems like emscripten can only access C atleast with the similar code that you have. I'm trying to get Lua into node. Its proving ... challenging.

 

How long these directions will last valid ??
I mean, DynamSoft site is one year old and is already obsolete.
Must bet Node.js will kill PHP but why learn emscripten now?
And let consider HTML5. Why not learn also?
Where are Webmasters go? Who know?
And CGI is obsolete?
My website rely still on an NGINX system !!!
I just have a headache.
Bye.

 

Soooo...I just learned that apt-get has an emscripten package. You can install it with sudo apt-get install emscripten and I'm assuming it works the same. I haven't tested it yet though.

 

With fastly moving things like this, you probably don't want to use that method for development setup. For the same reasons you don't install Node.js from default repo.

 

Agreed, and that is in fact what I ended up discovering.

 

Haha, well, launching an entire browser just to run some C is not something you see every day 😁

 

Good thing I'm not launching a browser 😉