Article: Creating Javascript bindings for C/C++ libraries with Emscripten

How to bind C/C++ libraries with Emscripten and use them from Javascript.

Tags: C++, Development, Emscripten, Javascript

In addition to allowing native code to run in the browser, Emscripten can also be used to facilitate interoperability between native code and Javascript. For raw C APIs, native functions can be called directly from Javascript with very little glue code. For C++ functions and classes, however, developers must explicitly specify bindings, using a declarative format.

Although it’s certainly possible to specify bindings for a library’s entire API, this can become cumbersome and time-consuming for larger, more complex libraries. Typically, I find it simpler to create a facade for the library, and provide bindings for the facade only. This provides all of the usual benefits of the facade pattern and, when using Embind, permits the creation of a more Javascript-flavoured interface than might otherwise be possible with the native API alone (key-value objects for configuration, Javascript function callbacks, etc.)

The sections below describe the details of using the two declarative binding systems supported by Emscripten: Embind and WebIDL.

Contents

Installing Emscripten

Install Emscripten using the instructions from the official site.

After installation is complete, ensure the directories containing the Emscripten executables are correctly added to the system PATH. If the supplied scripts to modify the PATH don’t work correctly, you may have to add the directories manually. (The directories are listed in the output of the ./emsdk activate command.)

Embind

Embind is the more flexible of the two binding implementations supported by Emscripten, and involves fewer source files and compilation steps. The ability to use the emscripten::val type for both return values and function/method arguments facilitates the creation of Javascript-friendly interfaces that interact directly with Javascript objects using only C++, without the need to implement an additional Javascript wrapper. For more information, see my article on creating Javascript-friendly interfaces using Embind.

Creating the bindings

Embind bindings are specified within C++ source code using the EMSCRIPTEN_BINDINGS() macro block, which is described in detail in the Embind documentation. Bindings can be specified for functions and classes, and C++ code can interoperate with Javascript code by using the emscripten::val class. Primitive types and strings (std::string and std::wstring) will be automatically converted between their C++ and Javascript equivalents.

Create a source file containing the interface which will be used to abstract access to the library, and specify the Embind bindings for your interface:

#include "MyLibrary.h"
#include <emscripten/bind.h>

using namespace emscripten;

bool TestLibrary()
{
	//Call library code here, and interact with Javascript
	//arguments, return values, and global variables using
	//the emscripten::val class
	
	//Primitive types and strings will automatically be converted
	//between their C++ and Javascript type equivalents
	return true;
}

class MyClass
{
	public:
		MyClass(int arg) {
			//Do something with arg
		}
		
		void method() {
			//Do something
		}
};

EMSCRIPTEN_BINDINGS(my_module)
{
	//Can be called from JS using Module.TestLibrary()
	function("TestLibrary", &TestLibrary);
	
	//Can be instantiated using new Module.MyClass()
	class_<MyClass>("MyClass")
	.constructor<int>()
	.function("method", &MyClass::method);
}

Compilation process

Compile the library itself to an LLVM bitcode shared library:

emconfigure ./configure --disable-static --enable-shared CXXFLAGS=-std=c++11 --prefix=/path/to/install/dir
emmake make
emmake make install

Compile the wrapper code to LLVM bitcode:

emcc -c -std=c++11 -I/path/to/install/dir/include MyWrapper.cpp -o MyWrapper.bc

Convert the bitcode for both the library and the wrapper to JavaScript (note that --bind is specified here during conversion, and not during compilation of the wrapper code):

emcc --bind /path/to/install/dir/lib/libMyLibrary.so MyWrapper.bc -o MyLibrary.js

The generated Javascript file can then be included in projects like any other Javascript source file, and the bound code can be accessed through the globally-accessible Emscripten Module object.

Notes

WebIDL

WebIDL is a W3 specification for describing interfaces to be implemented by web browsers. The interfaces that can be expressed using WebIDL are slightly more restrictive than those possible using Embind, and the WebIDL Binder does not support the Embind emscripten::val class. This means the creation of a Javascript-friendly interface requires the implementation of a Javascript wrapper. However, unlike Embind, WebIDL is a widely-supported standard, with broader uses than Emscripten alone.

Creating the bindings

WebIDL bindings are specified in a separate file to the C++ source code for the classes being bound. The bindings file is used to generate Javascript and C++ “glue” code, which are then linked together.

Create an IDL file containing the WebIDL binding declarations:

interface MyClass
{
	void MyClass(long arg);
	void method();
};

Alongside the IDL file, create a source file containing the implementation of the bound classes:

#include "MyLibrary.h"

class MyClass
{
	public:
		MyClass(int arg) {
			//Do something with arg
		}
		
		void method() {
			//Call library code here
		}
};

//Including the generated glue C++ file directly saves us having to write additional files, although
//doing so does mean that both C++ files will always be recompiled whenever either one changes
#include "glue.cpp"

Compilation process

First, compile the library we are wrapping to an LLVM bitcode shared library:

emconfigure ./configure --disable-static --enable-shared CXXFLAGS=-std=c++11 --prefix=/path/to/install/dir
emmake make
emmake make install

Next, use the WebIDL Binder script to process the IDL file and generate the Javascript and C++ “glue” code:

python /path/to/emscripten/tools/webidl_binder.py MyWrapper.idl glue

This will generate the files glue.js and glue.cpp in the current directory.

Compile the wrapper C++ code to LLVM bitcode (because we included glue.cpp in our wrapper C++ file, it will be compiled here too):

emcc -c -I/path/to/install/dir/include MyWrapper.cpp -o MyWrapper.bc

Finally, link everything together and convert it to Javascript, using the --post-js argument to include glue.js:

emcc /path/to/install/dir/lib/libMyLibrary.so MyWrapper.bc --post-js glue.js -o MyLibrary.js

The generated Javascript file can then be included in projects like any other Javascript source file, and the bound code can be accessed through the globally-accessible Emscripten Module object.