Article: Creating Javascript-friendly Emscripten interfaces with Embind

Tips for creating more Javascript-flavoured Emscripten interfaces.

Tags: C++, Development, Emscripten, Javascript

Important note: this article builds on the Embind information from my article on creating Javascript bindings for C/C++ libraries using Emscripten. If you are unfamiliar with using Embind, be sure to read that article before continuing with this one.

Embind makes it extremely easy to create interface bindings that allow Javascript code to interact with native code. However, usage of the generated API is often stylistically inclined more toward C++ than Javascript. With a couple of small touches, we can create interfaces with a more Javascript-flavoured API, that feel right at home alongside other Javascript libraries.

Contents

Javascript function callbacks

Assume that we have a class that supports using the observer pattern for notifying other objects of changes to its state:

class ObserverInterface
{
	public:
		virtual ~ObserverInterface();
		
		virtual void onEntityChanged() = 0;
};

class ObservableEntity
{
	public:
		void addObserver(ObserverInterface* observer);
		
		//...
};

We can allow the use of Javascript functions as observers, by creating a simple adapter class:

#include <emscripten/bind.h>
#include <emscripten.h>
#include <stdexcept>
#include <string>

class JavascriptFunctionObserver : public ObserverInterface
{
	public:
		JavascriptFunctionObserver(emscripten::val c) : callback(c) {}
		
		void onEntityChanged()
		{
			//Check that the callback is actually a function
			if (this->callback.typeof().as<std::string>() == "function")
			{
				//Call the Javascript callback function
				this->callback();
			}
			else {
				EM_ASM( throw 'callback is not a function'; );
			}
		}
		
	private:
		emscripten::val callback;
};

Assuming that we expose the following interface bindings:

class ObservableEntityWrapper
{
	private:
		ObservableEntity entity;
		
	public:
		void addListener(emscripten::val callback)
		{
			this->entity.addObserver( new JavascriptFunctionObserver(callback) );
		}
};

using namespace emscripten;
EMSCRIPTEN_BINDINGS(my_module)
{
	class_<ObservableEntityWrapper>("ObservableEntityWrapper")
	.constructor()
	.function("addListener", &ObservableEntityWrapper::addListener);
}

We can then add function callbacks like so:

var entity = new Module.ObservableEntityWrapper();
entity.addListener( function() {
	alert('Callback was invoked!');
} );

Key-value structures for configuration

Many Javascript libraries involve a single setup step that accepts a large number of configuration options, passed as a single object containing key-value mappings. This is quite straightforward to implement with Embind.

Assume we want to support the following usage from Javascript:

var lib = new Module.LibraryWrapper({
	verbosity: 3,
	opacity: 0.75,
	caption: "my caption",
	complete: function() { alert('Complete!'); }
});

We can support this usage with Embind by performing a little type jugging:

#include <emscripten/bind.h>
#include <string>

class LibraryWrapper
{
	private:
		//Determines if a JS value is of the specified type
		//If a property does not exist, we will see it as undefined
		bool isType(emscripten::val value, const std::string& type) {
			return (value.typeof().as<std::string>() == type);
		}
		
	public:
		LibraryWrapper(emscripten::val options)
		{
			emscripten::val optVerbosity = options["verbosity"];
			if (this->isType(optVerbosity, "number"))
			{
				int verbosity = optVerbosity.as<int>();
				//Do stuff with option value...
			}
			
			emscripten::val optOpacity = options["opacity"];
			if (this->isType(optOpacity, "number"))
			{
				double opacity = optOpacity.as<double>();
				//Do stuff with option value...
			}
			
			emscripten::val optCaption = options["caption"];
			if (this->isType(optCaption, "string"))
			{
				std::string caption = optCaption.as<std::string>();
				//Do stuff with option value...
			}
			
			emscripten::val optComplete = options["complete"];
			if (this->isType(optComplete, "function"))
			{
				//No need to typecast, we store function callbacks as emscripten::val
				//Do stuff with option value...
			}
		}
};

using namespace emscripten;
EMSCRIPTEN_BINDINGS(my_module)
{
	class_<LibraryWrapper>("LibraryWrapper")
	.constructor<emscripten::val>();
}

Any values that are missing, or of the wrong type, will simply be ignored. Combined with the code from above to facilitate the use of Javascript function callbacks, this allows our native library wrapper interface to support a stylistic usage similar to any other Javascript library.