C++ bindings
There are no binding macros to write: the generator reads your header and produces the bridge. In exchange it handles a constrained subset of C++. Stay inside it and everything binds for free; step outside and you wrap the offending API in something that does.
The rules
No raw pointers in the public API
Pointer arithmetic, ownership and aliasing have no JavaScript equivalent, so they are not exposed. Return values by value or through std::shared_ptr.
The public surface belongs in the header
The binder reads headers. A class declared in the .h but defined only in the .cpp, an anonymous namespace around the API, or a file-scope static function is invisible to it. Public members bind; private ones stay internal, which is fine.
Single inheritance only
Single-base virtual polymorphism is supported. Multiple inheritance - diamonds especially - breaks the auto-binder; refactor to composition or wrap it.
Templates need explicit instantiation
std::unique_ptr returned across the boundary fails silently - the call comes back null or undefined. Use std::shared_ptr for anything JavaScript will own a handle to.Primitive types
| C++ | JavaScript | Note |
|---|---|---|
void | undefined | |
bool | true / false | |
char, short, int and unsigned forms | Number | |
float, double | Number | |
long, unsigned long, int64_t, uint64_t | BigInt | both directions - pass 9n, not 9 |
std::string | String | |
std::optional<T> | the value or null | C++17, supported on every runtime |
emscripten::val | anything | the untyped escape hatch |
Vectors, maps and enums
Vectors of the primitive types are bound under generated names - VectorInt, VectorDouble, VectorString, VectorInt64 and friends. A vector of your own class follows the same rule: std::vector<MyClass> becomes VectorMyClass.
Two module helpers convert between the two worlds when you would rather work with plain arrays:
Maps bind the same way (MapIntInt, MapStringString, MapStringInt, MapIntString), enums arrive as objects with the enumerator names as keys, and both enum and enum class are supported.
std::vector return is a real vector proxy; through a worker bridge it arrives as a plain JavaScript array. m.toArray() accepts both shapes, so code written against it works either way.Exceptions
Throw from C++ and catch in JavaScript - that is the binding-friendly way to report failure, rather than status codes or out-parameters.
On wasm the message is "<type>: <what()>", with the halves also available as e.cppType and e.cppMessage. On React Native (JSI) it is the plain what() text.
Memory
There is no .delete() to call. Because no raw pointers cross the boundary, lifetime stays on the C++ side: destructors and shared_ptr reference counting do the work. Objects you hand to JavaScript are freed when the last reference goes away.
Wrapping what does not fit
Vendored library full of raw pointers, templates and multiple inheritance? Do not fight it - put a clean class in front of it. The wrapper is what binds; the upstream type stays internal.
App-side wrappers live in your own src/native; if you are publishing a package, put the wrapper inside the package so every consumer benefits.
TypeScript
Declarations are generated for every header and Rust import, outside your source tree under .cppjs/. Add the shared config as a dev dependency and extend it once:
Running with initNative({ useWorker: true })? Set dts: 'promise' in cppjs.config.js so every generated signature returns Promise<...>, matching the async runtime - and write await new X(...) for construction.
The escape hatch
When neither the rules nor a wrapper fit, hand-write a SWIG interface file next to the header and import it from JavaScript. It is also how you register a container the generator does not cover: