Andrew
Web development enthusiast with a keen interest in anything frontend.
Here we take a quick look at how to compile C++ code into Web Assembly (wasm) and get that running in a modern browser
WebAssembly is the assembly language for the web (duh!). It provides a way for browsers to execute code at near-native speed. For developers it allows important performance-sensitive components of webapps to be written in a more low-level language such as C++ and compile this into bytecode that can be accessed by Javascript in the front-end environment.
In this guide we are going to take a quick look at how to compile C++ to WebAssembly and interface with this from the javascript runtime.
We are going to be using Emscripten, a toolchain specialised in compiling to WebAssembly and asm.js (a performant sub-set of javascript). You can install the kit by following the Emscripten installation instructions.
If you are using WSL like I am you will need to also install CMake if you have not already (Linux x86_64).
You should have a project layout looking like this:
.
├── emsdk
├── src
│ ├── emscripten.h -> ../emsdk/./emscripten/incoming/system/include/emscripten.h
│ └── main.cpp
└── templates
└── shell_minimal.html
Source your emsdk environments:
./emsdk activate latest
source ./emsdk_env.sh
Emscripten will generate a Javascript wrapper for our Wasm code. We need to create the HTML template that Emscripten will use to populate with the generated JS code.
shell_minimal.html
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Emscripten-Generated Code</title>
</head>
<body>
{{{ SCRIPT }}}
</body>
</html>
Here {{{ SCRIPT }}}
will be substituted by the <script/>
that points to our JS wrapper for the wasm module
Script exposes Module
to the global scope. We can then reference this - useful when we have exported members. To export we need to define some functions in main.cpp
main.cpp
#include <stdio.h>
#include <iostream>
#include <emscripten/emscripten.h>
int main() {
cout << "Wasm Module initialized!" << endl;
}
#ifdef __cplusplus
extern "C" {
#endif
void EMSCRIPTEN_KEEPALIVE exportedFunction() {
cout << "Exported function has been called" << endl;
}
#ifdef __cplusplus
}
#endif
Here EMSCRIPTEN_KEEPALIVE
is important as it will ensure that myFunction
is included in the compilation (otherwise by default only main()
will be included). This is included as part of the emscripten.h
We can now use the commandline tool em++
(analogous to g++
) to compile our C++ code to Wasm and also produce wrapper JS + an HTML template file.
em++ -o index.html main.cpp -O3 -s WASM=1 --shell-file templates/shell_minimal.html -s NO_EXIT_RUNTIME=1 -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall"]'
This should produce the following artifacts:
index.html
- the file that includes our JS wrapperindex.js
- our JS wrapperindex.wasm
- our C++ code compiled to WasmTypically we should be able to run emrun
to serve the webpage but this has some issues in WSL. Instead you can use something like http-server
sudo npm install http-server -g to install a http server
http-server .
You should see that in the browser console there will be Wasm Module initialized!
printed. Success! The Wasm code has executed the main
entrypoint to the module but not the exported functions. These can be accessed from Javascript in the following way:
Module.ccall('exportedFunction', // name of C function
null, // return type
null, // argument types
null); // arguments
Reload your page and you should see
Exported function has been called
Web development enthusiast with a keen interest in anything frontend.