Converting C++ to Web Assembly

You can do it too

I have been hearing about Web Assembly for several years and I have been very interested in trying it out. I have a list of school projects which are in c++ and I would love to be able to display working versions of them. Web Assembly (wasm) promises to be a simple way to create a version that runs in a browser window.

The first step is to install the Emscripten compiler tool-chain on your development machine. My main computer uses Windows 10 build 2004 and I have Windows Subsystem for Linux 2 installed, and I am using Ubuntu 20 as my local Linux system. My first test of Web Assembly was to open vim and create a simple Hello World program in c++ and then run the emcc compiler on it. This is just as easy as compiling with emcc instead of g++ and passing in the flag WASM=1. To create a web ready target, you just need to give the output file the extension of .html. The Hello World compilation looks like

emcc hello.cpp -s WASM=1 -o hello.html

This creates an html file, a JavaScript file which acts as a glue layer, and a wasm file, which is the compiled binary module that will be executed. The file can be executed by opening it with SimpleHTTPServer from the CLI or perhaps Live Server, if you’re using VsCode. If the html output file is left out then it won’t be created, but the JavaScript file can be run from the command line using Node.js and Hello World will be printed.

At this point everything works, but what about the second c++ program that many people might write? Perhaps a program that prompts you for your name and then prints it out to the console. Well, this is where the c++ CLI environment and the browser environment begin to clash. Cout or endl will work just as you expect, but cin will not. Cin will open up a browser prompt if the program is opened in a web page and cin will create an infinite loop when it is opened in Node. This is a big stumbling block if one just wants to convert a few school projects so that they can be posted on a portfolio website.

This means that the most sensible way to use wasm is to handle small compute heavy sections in the front end, or to be a cross-platform alternative for embedded projects that can run Node as their runtime.

But…what if I want to get use input and don’t want to use the browser prompt? Well, that’s exactly what I wanted to do and while I’m not sure if my solution is the best, it did work.

The Web Assembly port is hosted here within the projects section on the home page and the source code is available in this GitHub repository.

The method which worked to replace a while loop was using the emscripten set main loop function. The first argument is the function that control flow will be passed to, the second argument is the number of times per second that the function will run, and the third is true for infinite loop.

emscripten_set_main_loop(<next_function>, 60, 1);

The first thing to know is that once the main function is left and a new function is set as the main loop in infinite mode, it is not possible to return to main. After exiting main with this architecture, each new function will need to call cancel main loop and then set a new main loop.

emscripten_cancel_main_loop();
emscripten_set_main_loop(<next_function>, 60

To handle the while loop, the next function would set a local variable and then call inline JavaScript which will check the value of a variable which is set in the client side JS file and updated with a submit event listener. Once the enter key is pressed, the JS variable will be updated (which in the following example is input) and can be checked in the inline JS. In this example I return an integer when the correct value is stored in the front-end JS variable and then check for that variable in a c++ if statement. This loop will run at the rate which was set in the second argument when it was called, so if one would like to print values to the console or the screen, then they should set the rate to a low value, like 1 and then turn it up to 60 times per second one everything works.

void start_game(void)
{
    int start_bool = 0;

    // Inline JS - Get value entered into the faux CLI
    start_bool = EM_ASM_INT({
        if (input == "") {
            return 5;
        }
    }, start_bool);

    // Enter is presse then start the game
    if (start_bool == 5) {
        emscripten_cancel_main_loop();
        emscripten_set_main_loop(next_function, 60, 1);
    }
}

This method of chaining functions will allow anyone to pass control flow between a set of functions, and to use some of them to take in user input just as they would have in the command line. If you have any questions about any of this then feel free to take a look at the working code in the repository or to reach out to me. Good luck in trying this out!

Leave a Reply