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 soon if (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 ready var 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 ready var 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
- http://kripken.github.io/emscripten-site/index.html
- http://webassembly.org
- https://developer.mozilla.org/en-US/docs/WebAssembly
Source Code
https://github.com/yushulx/asmjs
The post Learning Emscripten: Compile C/C++ to JavaScript appeared first on Code Pool.