Learning Emscripten: Compile C/C++ to JavaScript

Xiao Ling
Xiao Ling
Jul 24, 2017 · 5 min read

It was my first time to hear about WebAssembly when watching Google I/O ’17. WebAssembly (wasm) lets developers compile C/C++ or other statically typed languages into JavaScript for building high-performance web apps. Assume I have a C/C++ barcode detection or OCR library deployed on server-side, I can now move it to the web client-side. I was excited about this feature and wanted to know more about relevant technologies. My first step is to learn the compiler that called Emscripten.

What is Emscripten

“Emscripten is an LLVM-based project that compiles C and C++ into highly-optimizable JavaScript in asm.js format. This lets you run C and C++ on the web at near-native speed, without plugins.”

After reading the definition, you may have noticed that it doesn’t mention WebAssembly but asm.js. By default, Emscripten compiles C/C++ code into an asm.js file which is compatible with most of the web browsers. Whereas WebAssembly includes a wasm binary file and a JavaScript glue file:

WebAssembly-supported web browsers:

  • Firefox 52+
  • Chrome 57+
  • Latest Opera
  • Firefox 47+: enable the options.wasm flag in about:config
  • Chrome 51+: enable experimental WebAssembly flag in chrome://flags

Download and Installation

Getting Started

Create a hello.c file:

#include <stdio.h>int main() {printf("hello\n");return 0;}

Compile the C/C++ code:

emcc hello.c

There is an a.out.js file generated. We can run it using Node.js:

node a.out.js

To embed the code into a web page, use -o:

emcc hello.c -o hello.html

This command creates a hello.js file and a hello.html file. Open the HTML page in your web browser:

The a.out.js file and hello.js file are same:

Let’s try wasm option to see the difference:

emcc hello.c -s WASM=1 -o hello2.html

Something wrong here:

cannot use WASM=1 when full asm.js validation was disabled (make sure to run in at least -O1, and look for warnings about other options that might force asm.js off).

Add -O option to fix the error:

This command generates five new files:

If you double-click the hello2.html file in Chrome, you will see nothing:

The reason is that hello2.wasm file needs to be loaded via XHR, but Chrome does not support file:// XHR requests. You can use Firefox instead or deploy the page onto a server:

emrun hello2.html

Call C/C++ APIs

Emscripten eliminates dead code to minimize code size. Therefore, we have to export the functions that will be called by JavaScript. There are two ways to export native functions.

Export functions by command line

Write C code:

#include <stdio.h>#include <emscripten.h>char* world() {return "world";}int main() {printf("hello\n");return 0;}

Compile the code:

emcc -s EXPORTED_FUNCTIONS="['_world']" hello.c -o hello.html

Add following code to hello.html:

<button onclick="native()">click</button><script type='text/javascript'>function native() {var content = Module.ccall('world', 'string');alert(content);}</script>

You can also create an app.js file to run with Node.js:

var hello_module = require('./hello.js');console.log(hello_module.ccall('world', 'string'));

Define the function with EMSCRIPTEN_KEEPALIVE

Change the function:

char* EMSCRIPTEN_KEEPALIVE world() {return "world";}

Compile the hello.c file:

emcc hello.c -o hello.html

When clicking the button, you may see the error message: Assertion failed: the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits).

To fix the issue, add emscripten_exit_with_live_runtime() to main function:

int main() {printf("hello\n");emscripten_exit_with_live_runtime();return 0;}

Or, you can compile the code with -s NO_EXIT_RUNTIME=1:

emcc hello.c -o hello.html -s NO_EXIT_RUNTIME=1

Preload resource files

Using emcc, we can easily package files to .data file at compile time. Assume you have a license text file located in asset folder. Run the command:

emcc hello.c -o hello.html --preload-file ./asset

Create a new function to read the file:

void getLicense() {char license[256];int index = 0;FILE *file = fopen("./asset/license.txt", "r");if (!file) {printf("cannot open file\n");return;}while (!feof(file)) {char c = fgetc(file);if (c != EOF) {license[index++] = c;}}fclose (file);printf("%s\n", license);}

Module initialization status

If you want to customize the HTML page, you may have a question about how to tell when the module functions are ready. Take a look at the auto-generated JavaScript code:

setStatus: function(text) {if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };if (text === Module.setStatus.text) return;var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);var now = Date.now();if (m && now - Date.now() < 30) return; // if this is a progress update, skip it if too soonif (m) {text = m[1];progressElement.value = parseInt(m[2])*100;progressElement.max = parseInt(m[4])*100;progressElement.hidden = false;spinnerElement.hidden = false;} else {progressElement.value = null;progressElement.max = null;progressElement.hidden = true;if (!text) spinnerElement.style.display = 'none';}statusElement.innerHTML = text;}

If you debug the snippet, you will find when the status turns to ‘Running’, the module is loaded. It seems a little bit complicated. A better way is to send a notification from main() to JavaScript function:

int main() {EM_ASM(onLoaded());return 0;}

Define onLoaded() in custom.html:

<script>// called from main()function onLoaded() {alert('Module is loaded');}</script>

If you do not have a main() function, using onRuntimeInitialized() also works:

<script>// called when the runtime is readyvar Module = {onRuntimeInitialized: function () {alert('onRuntimeInitialized');}};</script>

Here is the final custom HTML page:

<!doctype html><html><head></head><body><h1>Custom Page</h1><button onclick="native()">click</button><script>// called when the runtime is readyvar Module = {onRuntimeInitialized: function () {alert('onRuntimeInitialized');}};// called from main()function onLoaded() {alert('Module is loaded');}function native() {var content = Module.ccall('world', 'string');alert(content);}</script><script async type="text/javascript" src="hello.js"></script></body></html>

Resources

Source Code

https://github.com/yushulx/asmjs

Originally published at www.codepool.biz on July 24, 2017.

Xiao Ling

Written by

Manager of Dynamsoft Open Source Projects | Tech Lover

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade