-
Notifications
You must be signed in to change notification settings - Fork 3.3k
WebIDL Binder
The WebIDL binder is the third tool providing bindings glue between C++ and JS in Emscripten. The first was the "bindings generator", which was a hackish experiment (despite its hackishness, it managed to be good enough for ammo.js and box2d.js). The second was Embind, which is fairly high-level and supports mapping sophisticated C++11 constructs between C++ and JS.
The goals of the WebIDL binder is to be low-level, efficient, and simpler than Embind, while robust and more maintainable than the bindings generator. Like Embind, it requires explicit declarations of what to bind together, but like the bindings generator, it does so at a low-level which is simpler to optimize.
The level at which it works is WebIDL, a language used to describe the interfaces between JS and the browser's native code in C++. WebIDL was designed for the particular purpose of gluing together C++ and JS, so it is a natural choice here; also, there are lots of already-written WebIDL files for browser APIs, which could eventually be reused.
The WebIDL binder is currently focused on wrapping C++ code so it is usable from JS (write a C++ class, use it as a library in JS as if it were a normal JS library), which is what the bindings generator does. Embind also supports wrapping in the other direction, which the WebIDL binder might do some day. In particular, it should be easy to let C++ call web APIs in a natural way by using WebIDL files describing those APIs.
For a complete working example, see test_webidl
in the test suite. As always, the test suite code is guaranteed to work, so it's a good place to learn from.
The first stage is to create a WebIDL file. That describes the C++ class you are going to wrap. This basically duplicates a little info from your C++ header file, in a format that is explicitly designed for easy parsing and to be able to represent things in a way that is convenient for connecting to JS.
For example, let's say you have these C++ classes:
class Foo {
public:
int getVal();
void setVal(int v);
};
class Bar {
public:
Bar(long val);
void doSomething();
};
You could write this IDL file to describe them:
[Constructor]
interface Foo {
long getVal();
void setVal(long v);
};
[Constructor(long val)]
interface Bar {
void doSomething();
};
Note how both are annotated as having a constructor (so you will be able to construct them from JS). Otherwise the definitions are fairly straightforward.
Note that types in WebIDL are not identically named to C++, for example IDL long
is a 32-bit integer, short
is a 16-bit integer, etc. There are also types like DOMString which represent a JS string.
To generate bindings code, run
python tools/webidl_binder.py my_classes.idl glue
where my_classes.idl
is the name of that IDL file, and glue is the name of the output file you want. The bindings generator will emit two files: glue.cpp
and glue.js
.
To use those files, you should
- Add
--post-js glue.js
in your final emcc command, so that it is included (at the end of your normal output). - Add a cpp file to that emcc command, which contains something like
#include<..all the stuff the glue code needs, basically headers for the classes you are binding>
#include<glue.cpp>
That way your emcc command will include both the C++ glue and the JS glue, which are built to work together. The output should contain everything you need, with the classes now usable through JS.
Continuing the above example, you can write things like
var f = new Module.Foo();
f.setVal(200);
alert(f.getVal());
var b = new Module.Bar(123);
b.doSomething();
and so forth.
destroy
TODO
Imagine you have a class that has a virtual method called from C++, and you want to subclass it and implement it in JS. To do so, you can use the JSImplementation option, for example in this IDL:
[Constructor, JSImplementation="Base"]
interface ImplJS {
void virtualFunc();
void virtualFunc2();
};
Base
is the C++ class, and ImplJS does not exist in your C++ code. JSImplementation="Base"
means "this class will be a JS implementation of Base". After running the bindings generator and compiling, you can do this:
var c = new ImplJS();
c.virtualFunc = function() { .. };
When C++ code has a pointer to a Base
instance and calls virtualFunc
, that call will reach the JS code written here.
Note that you must implement all the methods you mentioned in the IDL of the JSImplementation class (ImplJS). If not, then an error will be shown (the technical reason is that C++ implements the virtual method, in a way that calls into JS. If there is nothing in JS to be called, it goes up through the prototype chain and calls that same C++ function once more).
README.md ``