Introduction
WebAssembly (Wasm) is a binary instruction format for a stack-based virtual machine. It is designed as a portable target for compilation of high-level languages like C++, enabling deployment on the web for client and server applications. Emscripten is an open-source compiler toolchain that allows you to compile C++ code into WebAssembly. This tutorial will guide you through the process of using the Emscripten toolchain to compile C++ code into WebAssembly and run it in a web environment.
Prerequisites
Before diving into the tutorial, ensure you have the following:
- Basic understanding of C++.
- Familiarity with web development (HTML, JavaScript).
- Installed Node.js and npm.
- Installed Python (Emscripten uses Python scripts).
Installing Emscripten
To start using Emscripten, you first need to install it. Emscripten requires several dependencies, which can be easily managed using its SDK (emsdk).
Step 1: Clone the Emscripten SDK
First, clone the emsdk repository:
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
Code language: Shell Session (shell)
Step 2: Install and Activate the Latest SDK
Next, install the latest SDK and activate it:
./emsdk install latest
./emsdk activate latest
Code language: Shell Session (shell)
Step 3: Set Up Environment Variables
Set up the environment variables to make the emsdk tools available in your terminal session:
source ./emsdk_env.sh
Code language: Shell Session (shell)
You might want to add the above line to your .bashrc
or .zshrc
file to set up the environment variables automatically for every session.
Compiling C++ Code to WebAssembly
Step 1: Writing a Simple C++ Program
Let’s start with a simple C++ program. Create a file named hello.cpp
with the following content:
#include <iostream>
int main() {
std::cout << "Hello, WebAssembly!" << std::endl;
return 0;
}
Code language: C++ (cpp)
Step 2: Compiling with Emscripten
To compile the C++ code to WebAssembly, use the emcc
compiler provided by Emscripten. Run the following command:
emcc hello.cpp -o hello.html
Code language: Shell Session (shell)
This command tells Emscripten to compile hello.cpp
and generate an HTML file named hello.html
along with the corresponding WebAssembly and JavaScript files.
Step 3: Understanding the Output
Emscripten generates several files:
hello.html
– An HTML file to load and run the WebAssembly module.hello.js
– A JavaScript file that loads the WebAssembly module.hello.wasm
– The WebAssembly binary file.
You can open hello.html
in a web browser to see the output of your C++ program.
Integrating WebAssembly with JavaScript
While running C++ code in a browser is interesting, the true power of WebAssembly comes from its ability to interact with JavaScript. Let’s modify our example to demonstrate this interaction.
Step 1: Modifying the C++ Program
Update hello.cpp
to expose a function to JavaScript:
#include <emscripten.h>
extern "C" {
EMSCRIPTEN_KEEPALIVE
void sayHello() {
std::cout << "Hello from WebAssembly!" << std::endl;
}
}
Code language: C++ (cpp)
The EMSCRIPTEN_KEEPALIVE
macro ensures that the sayHello
function is not removed by Emscripten’s optimizer. The extern "C"
block prevents C++ name mangling, making the function name predictable in JavaScript.
Step 2: Compiling with Emscripten
Compile the updated C++ code:
emcc hello.cpp -o hello.js -s EXPORTED_FUNCTIONS='["_sayHello"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]'
Code language: Shell Session (shell)
The -s EXPORTED_FUNCTIONS
option specifies the functions to be exposed to JavaScript. The -s EXTRA_EXPORTED_RUNTIME_METHODS
option includes additional runtime methods that facilitate interaction between JavaScript and WebAssembly.
Step 3: Writing JavaScript to Call WebAssembly Functions
Create an HTML file named index.html
with the following content:
<!DOCTYPE html>
<html>
<head>
<title>WebAssembly with Emscripten</title>
</head>
<body>
<h1>WebAssembly with Emscripten</h1>
<button id="sayHelloButton">Say Hello</button>
<script src="hello.js"></script>
<script>
var sayHelloButton = document.getElementById("sayHelloButton");
sayHelloButton.addEventListener("click", function() {
Module.ccall("sayHello", null, [], []);
});
</script>
</body>
</html>
Code language: HTML, XML (xml)
Step 4: Running the Application
Serve the index.html
file using a local web server. You can use a simple Python HTTP server:
python -m http.server
Code language: Shell Session (shell)
Open your browser and navigate to http://localhost:8000
. Click the “Say Hello” button to see the interaction between JavaScript and WebAssembly.
Advanced Emscripten Features
File System Integration
Emscripten provides a virtual file system that allows your WebAssembly code to read and write files. This can be useful for scenarios like processing user-uploaded files or saving data locally.
Step 1: Modifying the C++ Program
Update hello.cpp
to demonstrate file system usage:
#include <emscripten.h>
#include <stdio.h>
extern "C" {
EMSCRIPTEN_KEEPALIVE
void writeFile() {
FILE* file = fopen("/hello.txt", "w");
if (file) {
fputs("Hello, File System!", file);
fclose(file);
}
}
EMSCRIPTEN_KEEPALIVE
void readFile() {
FILE* file = fopen("/hello.txt", "r");
if (file) {
char buffer[100];
fgets(buffer, sizeof(buffer), file);
fclose(file);
printf("File content: %s\n", buffer);
}
}
}
Code language: C++ (cpp)
Step 2: Compiling with Emscripten
Compile the updated C++ code:
emcc hello.cpp -o hello.js -s EXPORTED_FUNCTIONS='["_writeFile", "_readFile"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' --preload-file hello.txt
Code language: Shell Session (shell)
The --preload-file
option bundles the specified file with the WebAssembly module.
Step 3: Writing JavaScript to Call File System Functions
Update index.html
to include buttons for writing and reading the file:
<!DOCTYPE html>
<html>
<head>
<title>WebAssembly with Emscripten</title>
</head>
<body>
<h1>WebAssembly with Emscripten</h1>
<button id="writeFileButton">Write File</button>
<button id="readFileButton">Read File</button>
<script src="hello.js"></script>
<script>
var writeFileButton = document.getElementById("writeFileButton");
writeFileButton.addEventListener("click", function() {
Module.ccall("writeFile", null, [], []);
});
var readFileButton = document.getElementById("readFileButton");
readFileButton.addEventListener("click", function() {
Module.ccall("readFile", null, [], []);
});
</script>
</body>
</html>
Code language: HTML, XML (xml)
Step 4: Running the Application
Serve the index.html
file using a local web server and open it in your browser. Click the “Write File” and “Read File” buttons to see file system operations in action.
Using C++ Libraries with Emscripten
Emscripten supports many popular C++ libraries, allowing you to leverage existing codebases in your WebAssembly projects. In this section, we’ll demonstrate how to use the SDL2 library with Emscripten.
Step 1: Installing SDL2
Emscripten includes precompiled versions of several libraries, including SDL2. You can install SDL2 using emsdk:
./emsdk install sdl2
./emsdk activate sdl2
Step 2: Writing an SDL2 Program
Create a file named sdl_example.cpp
with the following content:
#include <SDL.h>
#include <emscripten.h>
SDL_Window* window;
SDL_Renderer* renderer;
void main_loop() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
emscripten_cancel_main_loop();
return;
}
}
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
SDL_RenderPresent(renderer);
}
int main() {
SDL_Init(SDL_INIT_VIDEO);
window = SDL_CreateWindow("SDL2 Emscripten Example", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 800, 600, SDL_WINDOW_SHOWN);
renderer = SDL_CreateRenderer(window, -1, 0);
em
scripten_set_main_loop(main_loop, 0, 1);
return 0;
}
Code language: C++ (cpp)
Step 3: Compiling with Emscripten
Compile the SDL2 program:
emcc sdl_example.cpp -o sdl_example.html -s USE_SDL=2
Code language: Shell Session (shell)
The -s USE_SDL=2
option tells Emscripten to link against the SDL2 library.
Step 4: Running the Application
Serve the sdl_example.html
file using a local web server and open it in your browser. You should see a window with a black background and a red rectangle.
Debugging WebAssembly
Debugging WebAssembly can be challenging due to its binary nature. However, Emscripten provides several tools and techniques to make debugging easier.
Source Maps
Emscripten can generate source maps to map the WebAssembly code back to the original C++ source code. This allows you to use browser developer tools to debug your C++ code.
Step 1: Compiling with Source Maps
Compile your C++ code with the -g
option to generate source maps:
emcc hello.cpp -o hello.js -g
Code language: Shell Session (shell)
Step 2: Using Browser Developer Tools
Open the HTML file in your browser and open the developer tools (usually by pressing F12). In the “Sources” tab, you should see the original C++ source files. You can set breakpoints, step through code, and inspect variables just like you would with JavaScript.
Logging and Console Output
You can use standard C++ functions like printf
and std::cout
to log messages to the browser console. This can be useful for debugging and understanding the flow of your program.
Optimizing WebAssembly
Emscripten provides several optimization options to reduce the size and improve the performance of your WebAssembly modules.
Step 1: Compiling with Optimizations
Use the -O
options to enable optimizations. For example, -O2
enables moderate optimizations, while -O3
enables aggressive optimizations:
emcc hello.cpp -o hello.js -O2
Code language: Shell Session (shell)
Step 2: Using Link-Time Optimization
Link-Time Optimization (LTO) can further reduce the size of your WebAssembly modules by performing optimizations across all compilation units. Use the -flto
option to enable LTO:
emcc hello.cpp -o hello.js -O2 -flto
Code language: Shell Session (shell)
Step 3: Minifying JavaScript
Emscripten can minify the generated JavaScript code to reduce its size. Use the --closure 1
option to enable Closure Compiler:
emcc hello.cpp -o hello.js -O2 --closure 1
Code language: Shell Session (shell)
Conclusion
In this tutorial, we covered the basics of using the Emscripten toolchain to compile C++ code to WebAssembly. We explored various features, including integrating WebAssembly with JavaScript, using the virtual file system, leveraging existing C++ libraries, debugging, and optimizing WebAssembly modules. With these skills, you can start building high-performance web applications using C++ and WebAssembly.
WebAssembly is a powerful technology that enables you to run C++ code in the browser with near-native performance. Emscripten makes it easy to compile your existing C++ codebases to WebAssembly and integrate them with modern web technologies. As you continue to explore WebAssembly and Emscripten, you’ll discover even more possibilities for creating fast, efficient, and feature-rich web applications.
Additional Resources
- Emscripten Documentation
- WebAssembly Documentation
- MDN Web Docs: Using WebAssembly
- Emscripten GitHub Repository
By following this tutorial and exploring the additional resources, you’ll gain a deeper understanding of WebAssembly and how to effectively use Emscripten to bring your C++ applications to the web. Happy coding!