:sparkles: Magical headers that make your C++ library accessible from JavaScript :rocket:
Quick start | Requirements | Features | User guide | Contributing | License
nbindis a set of headers that make your C++11 library accessible from JavaScript. With a single
#includestatement, your C++ compiler generates the necessary bindings without any additional tools. Your library is then usable as a Node.js addon or, if compiled to asm.js with Emscripten, directly in web pages without any plugins.
nbindworks with the autogypi dependency management tool, which sets up
node-gypto compile your library without needing any configuration (other than listing your source code file names).
nbindis MIT licensed and based on templates and macros inspired by embind.
C++ everywhere in 5 easy steps using Node.js,
nbindand autogypi:
Starting point | Step 1 - bind | Step 2 - prepare |
---|---|---|
Original C++ code hello.cc :#include <string> #include <iostream> struct Greeter { static void sayHello( std::string name ) { std::cout << "Hello, " << name << "!\n"; } }; |
List your classes and methods:// Your original code here // Add these below it: #include "nbind/nbind.h" NBIND_CLASS(Greeter) { method(sayHello); } |
Add scripts to package.json :{ "scripts": { "autogypi": "autogypi", "node-gyp": "node-gyp", "emcc-path": "emcc-path", "copyasm": "copyasm", "ndts": "ndts" } } |
Step 3 - install | Step 4 - build | Step 5 - use! |
Run on the command line:npm install --save \ nbind autogypi node-gyp npm run -- autogypi \ --init-gyp \ -p nbind -s hello.cc |
Compile to native binary:npm run -- node-gyp \ configure buildOr to Asm.js: npm run -- node-gyp \ configure build \ --asmjs=1 |
Call from Node.js:var nbind = require('nbind'); var lib = nbind.init().lib; lib.Greeter.sayHello('you');Or from a web browser (see below). |
The above is all of the required code. Just copy and paste in the mentioned files and prompts or take a shortcut:
git clone https://github.com/charto/nbind-example-minimal.git cd nbind-example-minimal npm install && npm test
See it run!
(Note: nbind-example-universal is a better starting point for development)
You need:
node-gyp, see instructions).
And one of the following C++ compilers:
nbindallows you to:
.d.tsdefinition files from C++ code for IDE autocompletion and compile-time checks of JavaScript side code.
In more detail:
The goal is to provide a stable API for binding C++ to JavaScript. All internals related to JavaScript engines are hidden away, and a single API already supports extremely different platforms.
Target | Development platform | |
---|---|---|
Linux / OS X | Windows | |
Native |
|
|
Asm.js |
|
Tested manually |
More is coming! Work is ongoing to:
Future
0.x.yversions should remain completely backwards-compatible between matching
xand otherwise with minor changes. Breaking changes will be listed in release notes of versions where
yequals
0.
Please report issues through Github and mention the platform you're targeting (Node.js, asm.js, Electron or something else). Pull requests are very welcome.
Warning: rebase is used within develop and feature branches (but not master).
When developing new features, writing tests first works best. If possible, please try to get them working on both Node.js and asm.js. Otherwise your pull request will get merged to Master only after maintainer(s) have fixed the other platform.
Installing Emscripten to develop for asm.js can be tricky. It will require Python 2.7 and setting paths correctly, please refer to Emscripten documentation. The
bin/emccscript in this package is just a wrapper, the actual
emcccompiler binary should be in your path.
You can rebuild the asm.js library and run tests as follows:
npm run clean-asm && npm run prepublish && npm run test-asm
nbindexamples shown in this user guide are also available to download for easier testing as follows:
Extract this zip package or run:
git clone https://github.com/charto/nbind-examples.git
Enter the examples directory and install:
cd nbind-examples npm install
Once you have all requirements installed, run:
npm init npm install --save nbind autogypi node-gyp
nbind,
autogypiand
node-gypare all needed to compile a native Node.js addon from source when installing it. If you only distribute an asm.js version, you can use
--save-devinstead of
--savebecause users won't need to compile it.
Next, to run commands without installing them globally, it's practical to add them in the
scriptssection of your
package.jsonthat
npm initjust generated. Let's add an install script as well:
"scripts": { "autogypi": "autogypi", "node-gyp": "node-gyp", "emcc-path": "emcc-path", "copyasm": "copyasm","install": "autogypi && node-gyp configure build"
}
emcc-pathis needed internally by
nbindwhen compiling for asm.js. It fixes some command line options that
node-gypigenerates on OS X and the Emscripten compiler doesn't like. You can leave it out if only compiling native addons.
The
installscript runs when anyone installs your package. It calls
autogypiand then uses
node-gypto compile a native addon.
autogypiuses npm package information to set correct include paths for C/C++ compilers. It's needed when distributing addons on npm so the compiler can find header files from the
nbindand
nanpackages installed on the user's machine. Initialize it like this:
npm run -- autogypi --init-gyp -p nbind -s hello.cc
Replace
hello.ccwith the name of your C++ source file. You can add multiple
-soptions, one for each source file.
The
-p nbindmeans the C++ code uses
nbind. Multiple
-poptions can be added to add any other packages compatible with
autogypi.
The
--init-gypcommand generates files
binding.gypand
autogypi.jsonthat you should distribute with your package, so that
autogypiand
node-gypwill know what to do when the
installscript runs.
Now you're ready to start writing code and compiling.
Refer to autogypi documentation to set up dependencies of your package, and how other packages should include it if it's a library usable directly from C++.
--asmjs=1is the only existing configuration option for
nbinditself. You pass it to
node-gypby calling it like
node-gyp configure build --asmjs=1. It compiles your package using Emscripten instead of your default C++ compiler and produces asm.js output.
First
nbindneeds to be initialized by calling
nbind.initwhich takes the following optional arguments:
process.cwd()and
__dirnameis a good alternative.
nbindwill be added as members. Default is an empty object. Any existing options will be seen by asm.js code and can be used to configure Emscripten output. Must follow base path (which may be set to
nullor
undefined).
null.
nbindcan be initialized synchronously on Node.js and asynchronously on browsers and Node.js. Purely synchronous is easier but not as future-proof:
var nbind = require('nbind'); var lib = nbind.init().lib;// Use the library.
Using a callback also supports asynchronous initialization:
var nbind = require('nbind');nbind.init(function(err, binding) { var lib = binding.lib;
// Use the library. });
The callback passed to init currently gets called synchronously in Node.js and asynchronously in browsers. To avoid releasing zalgo you can for example wrap the call in a bluebird promise:
var bluebird = require('bluebird'); var nbind = require('nbind');bluebird.promisify(nbind.init)().then(function(binding) { var lib = binding.lib;
// Use the library. });
There are two possible files to include:
nbind/api.hfor using types from the
nbindnamespace such as JavaScript callbacks inside your C++ code.
#includebefore your own class definitions.
nbind.
nbind/nbind.hfor exposing your C++ API to JavaScript.
#includeafter your own class definitions to avoid accidentally invoking its macros.
Use
#include "nbind/nbind.h"at the end of your source file with only the bindings after it. The header defines macros with names like
constructand
methodthat may otherwise break your code or conflict with other headers.
It's OK to include
nbind/nbind.halso when not targeting any JavaScript environment.
node-gypdefines a
BUILDING_NODE_EXTENSIONmacro and Emscripten defines an
EMSCRIPTENmacro so when those are undefined, the include file does nothing.
Use
#include "nbind/api.h"in your header files to use types in the nbind namespace if you need to report errors without throwing exceptions, or want to pass around callbacks or objects.
You can use an
#ifdef NBIND_CLASSguard to skip your
nbindexport definitions when the headers weren't loaded.
Example that uses an
nbindcallback in C++ code:
1-headers.cc
#include #include// For nbind::cbFunction type. #include "nbind/api.h"
class HeaderExample {
public:
static void callJS(nbind::cbFunction &callback) { std::cout << "JS says: " << callback.call<:string>(1, 2, 3); }
};
// For NBIND_CLASS() and method() macros. #include "nbind/nbind.h"
#ifdef NBIND_CLASS
NBIND_CLASS(HeaderExample) { method(callJS); }
#endif </:string>
Example used from JavaScript:
1-headers.js
var nbind = require('nbind');var lib = nbind.init().lib;
lib.HeaderExample.callJS(function(a, b, c) { return('sum = ' + (a + b + c) + '\n'); });
Run the example with
node 1-headers.jsafter installing. It prints:
JS says: sum = 6
Functions not belonging to any class are exported inside an
NBIND_GLOBALblock with a macro call
function(functionName);which takes the name of the function as an argument (without any quotation marks). The C++ function gets exported to JavaScript with the same name, or it can be renamed by adding a second argument (with quotation marks):
function(cppFunctionName, "jsExportedName");
If the C++ function is overloaded,
multifunctionmacro must be used instead. See overloaded functions.
Note: you cannot put several
function(...);calls on the same line! Otherwise you'll get an error about redefining a symbol.
Example:
6-functions.cc
#includevoid sayHello(std::string name) { std::cout << "Hello, " << name << "!\n"; }
#include "nbind/nbind.h"
NBIND_GLOBAL() { function(sayHello); }
Example used from JavaScript:
6-functions.js
var nbind = require('nbind'); var lib = nbind.init().lib;lib.sayHello('you');
The
NBIND_CLASS(className)macro takes the name of your C++ class as an argument (without any quotation marks), and exports it to JavaScript using the same name. It's followed by a curly brace enclosed block of method exports, as if it was a function definition.
The class can be renamed on the JavaScript side by passing a string as a second argument. This is especially useful for binding a template class specialization with a more reasonable name:
NBIND_CLASS(Data, "IntData")
Constructors are exported with a macro call
construct();where
typesis a comma-separated list of arguments to the constructor, such as
int, int. Calling
constructmultiple times allows overloading it, but each overload must have a different number of arguments.
Constructor arguments are the only types that
nbindcannot detect automatically.
Example with different constructor argument counts and types:
2-classes.cc
#includeclass ClassExample {
public:
ClassExample() { std::cout << "No arguments\n"; } ClassExample(int a, int b) { std::cout << "Ints: " << a << " " << b << "\n"; } ClassExample(const char *msg) { std::cout << "String: " << msg << "\n"; }
};
#include "nbind/nbind.h"
NBIND_CLASS(ClassExample) { construct<>(); construct(); construct(); }
Example used from JavaScript:
2-classes.js
var nbind = require('nbind');var lib = nbind.init().lib;
var a = new lib.ClassExample(); var b = new lib.ClassExample(42, 54); var c = new lib.ClassExample("Don't panic");
Run the example with
node 2-classes.jsafter installing. It prints:
No arguments Ints: 42 54 String: Don't panic
When a C++ class inherits another, the
inheritmacro can be used to allow calling parent class methods on the child class, or passing child class instances to C++ methods expecting parent class instances.
Internally JavaScript only has prototype-based single inheritance while C++ supports multiple inheritance. To simulate it, nbind will use one parent class as the child class prototype, and copy the contents of the other parents to the prototype. This has otherwise the same effect, except the JavaScript
instanceofoperator will return
truefor only one of the parent classes.
Example:
NBIND_CLASS(Child) { inherit(FirstParent); inherit(SecondParent); }
Methods are exported inside an
NBIND_CLASSblock with a macro call
method(methodName);which takes the name of the method as an argument (without any quotation marks). The C++ method gets exported to JavaScript with the same name.
If the C++ method is overloaded,
multimethodmacro must be used instead. See overloaded functions.
Properties should be accessed through getter and setter functions.
Data types of method arguments and its return value are detected automatically so you don't have to specify them. Note the supported data types because using other types may cause compiler errors that are difficult to understand.
If the method is
static, it becomes a property of the JavaScript constructor function and can be accessed like
className.methodName(). Otherwise it becomes a property of the prototype and can be accessed like
obj = new className(); obj.methodName();
Example with a method that counts a cumulative checksum of ASCII character values in strings, and a static method that processes an entire array of strings:
3-methods.cc
#include #includeclass MethodExample {
public:
unsigned int add(std::string part) { for(char &c : part) sum += c;
return(sum);
}
static std::vector check(std::vector<:string> list) { std::vector result; MethodExample example;
for(auto &&part : list) result.push_back(example.add(part)); return(result);
}
unsigned int sum = 0;
};
#include "nbind/nbind.h"
NBIND_CLASS(MethodExample) { construct<>();
method(add); method(check); } </:string>
Example used from JavaScript, first calling a method in a loop from JS and then a static method returning an array:
3-methods.js
var nbind = require('nbind');var lib = nbind.init().lib;
var parts = ['foo', 'bar', 'quux'];
var checker = new lib.MethodExample();
console.log(parts.map(function(part) { return(checker.add(part)); }));
console.log(lib.MethodExample.check(parts));
Run the example with
node 3-methods.jsafter installing. It prints:
[ 324, 633, 1100 ] [ 324, 633, 1100 ]
The example serves to illustrate passing data. In practice, such simple calculations are faster to do in JavaScript rather than calling across languages because copying data is quite expensive.
The
function()and
method()macroes cannot distinguish between several overloaded versions of the same function or method, causing an error. In this case the
multifunction()and
multimethod()macroes must be used.
Their second parameter is a list of argument types wrapped in an
args()macro to select a single overloaded version.
For example consider an overloaded method:
void test(unsigned int x) const; void test(unsigned int x, unsigned int y) const;
In bindings, one of the versions needs to be explicitly selected. The second of the two would be referenced like:
multimethod(test, args(unsigned int, unsigned int));
As always, the return type and method constness are autodetected.
For calling from JavaScript, additionally each overload needs to have a distinct name. For renaming an overload JavaScript will see, the binding code is like:
multimethod(test, args(unsigned int, unsigned int), "test2");
You can then write a JavaScript wrapper to inspect arguments and select which overload to call. The reason for this is, that
nbindbinds a JavaScript property to a single C++ function pointer, which wraps one overloaded version of the function with type conversion code.
Otherwise, it would need to generate a new C++ function that also checks the arguments. This would result in a larger native binary without any speed advantage.
Property getters are exported inside an
NBIND_CLASSblock with a macro call
getter(getterName)with the name of the getter method as an argument.
nbindautomatically strips a
get/
Get/
get_/
Get_prefix and converts the next letter to lowercase, so for example
getXand
get_xboth would become getters of
xto be accessed like
obj.x
Property setters are exported together with getters using a macro call
getset(getterName, setterName)which works much like
getter(getterName)above. Both
getterNameand
setterNameare mangled individually so you can pair
getXwith
set_xif you like. From JavaScript,
++obj.xwould then call both of them to read and change the property.
Example class and property with a getter and setter:
4-getset.cc
class GetSetExample {public:
void setValue(int value) { this->value = value; } int getValue() { return(value); }
private:
int value = 42;
};
#include "nbind/nbind.h"
NBIND_CLASS(GetSetExample) { construct<>();
getset(getValue, setValue); }
Example used from JavaScript:
4-getset.js
var nbind = require('nbind');var lib = nbind.init().lib;
var obj = new lib.GetSetExample();
console.log(obj.value++); // 42 console.log(obj.value++); // 43
Run the example with
node 4-getset.jsafter installing.
nbindsupports automatically converting between JavaScript arrays and C++
std::vectoror
std::arraytypes. Just use them as arguments or return values in C++ methods.
Note that data structures don't use the same memory layout in both languages, so the data always gets copied which takes more time for more data. For example the strings in an array of strings also get copied, one character at a time. In asm.js data is copied twice, first to a temporary space using a common format both languages can read and write.
Callbacks can be passed to C++ methods by simply adding an argument of type
nbind::cbFunction &to their declaration.
They can be called with any number of any supported types without having to declare in any way what they accept. The JavaScript code will receive the parameters as JavaScript variables to do with them as it pleases.
A callback argument
argcan be called like
arg("foobar", 42);in which case the return value is ignored. If the return value is needed, the callback must be called like
arg.call("foobar", 42);where type is the desired C++ type that the return value should be converted to. This is because the C++ compiler cannot otherwise know what the callback might return.
Warning: while callbacks are currently passed by reference, they're freed after the called C++ function returns! That's intended for synchronous functions like
Array.mapwhich calls a callback zero or more times and then returns. For asynchronous functions like
setTimeoutwhich calls the callback after it has returned, you need to copy the argument to a new
nbind::cbFunctionand store it somewhere.
C++ objects can be passed to and from JavaScript using different parameter and return types in C++ code:
const)
Note: currently passing objects by pointer on Node.js requires the class to have a "copy constructor" initializing itself from a pointer. This will probably be fixed later.
Returned pointers and references can be
const, in which case calling their non-const methods or passing them as non-const parameters will throw an error. This prevents causing undefined behaviour corresponding to C++ code that wouldn't even compile.
Using pointers and references is particularly:
Passing data by value using value objects solves both issues. They're based on a
toJSfunction on the C++ side and a
fromJSfunction on the JavaScript side. Both receive a callback as an argument, and calling it with any parameters calls the constructor of the equivalent type in the other language.
The callback on the C++ side is of type
nbind::cbOutput. Value objects are passed through the C++ stack to and from the exported function.
nbinduses C++11 move semantics to avoid creating some additional copies on the way.
The equivalent JavaScript constructor must be registered on the JavaScript side by calling
binding.bind('CppClassName', JSClassName)so that
nbindknows which types to translate between each other.
Example with a class
Coordused as a value object, and a class
ObjectExamplewhich uses objects passed by values and references:
5-objects.cc
#include#include "nbind/api.h"
class Coord {
public:
Coord(signed int x = 0, signed int y = 0) : x(x), y(y) {} explicit Coord(const Coord *other) : x(other->x), y(other->y) {}
void toJS(nbind::cbOutput output) { output(x, y); }
signed int getX() { std::cout << "Get X\n"; return(x); } signed int getY() { std::cout << "Get Y\n"; return(y); }
void setX(signed int x) { this->x = x; } void setY(signed int y) { this->y = y; }
signed int x, y;
};
class ObjectExample {
public:
static void showByValue(Coord coord) { std::cout << "C++ value " << coord.x << ", " << coord.y << "\n"; }
static void showByRef(Coord *coord) { std::cout << "C++ ref " << coord->x << ", " << coord->y << "\n"; }
static Coord getValue() { return(Coord(12, 34)); }
static Coord *getRef() { static Coord coord(56, 78); return(&coord); }
};
#include "nbind/nbind.h"
NBIND_CLASS(Coord) { construct<>(); construct(); construct();
getset(getX, setX); getset(getY, setY); }
NBIND_CLASS(ObjectExample) { method(showByValue); method(showByRef); method(getValue); method(getRef); }
Example used from JavaScript:
5-objects.js
var nbind = require('nbind');var binding = nbind.init(); var lib = binding.lib;
function Coord(x, y) { this.x = x; this.y = y; }
Coord.prototype.fromJS = function(output) { output(this.x, this.y); }
Coord.prototype.show = function() { console.log('JS value ' + this.x + ', ' + this.y); }
binding.bind('Coord', Coord);
var value1 = new Coord(123, 456); var value2 = lib.ObjectExample.getValue(); var ref = lib.ObjectExample.getRef();
lib.ObjectExample.showByValue(value1); lib.ObjectExample.showByValue(value2); value1.show(); value2.show();
lib.ObjectExample.showByRef(ref); console.log('JS ref ' + ref.x + ', ' + ref.y);
Run the example with
node 5-objects.jsafter installing. It prints:
C++ value 123, 456 C++ value 12, 34 JS value 123, 456 JS value 12, 34 C++ ref 56, 78 Get X Get Y JS ref 56, 78
Parameters and return values of function calls between languages are automatically converted between equivalent types:
| JavaScript | C++ | | ---------- | ------------------------------------------- | | number | (
un)
signed char,
short,
int,
long| | number |
float,
double| | number or bignum | (
un)
signed long,
long long| | boolean |
bool| | string |
const(
unsigned)
char *| | string |
std::string| | Array |
std::vector| | Array |
std::array| | Function |
nbind::cbFunction
nbind::Bufferstruct
Type conversion is customizable by passing policies as additional arguments to
construct,
functionor
methodinside an
NBIND_CLASSor
NBIND_GLOBALblock. Currently supported policies are:
nbind::Nullable()allows passing
nullas an argument when a C++ class instance is expected. The C++ function will then receive a
nullptr.
nbind::Strict()enables stricter type checking. Normally anything in JavaScript can be converted to
number,
stringor
booleanwhen expected by a C++ function. This policy requires passing the exact JavaScript type instead.
Type conversion policies are listed after the method or function names, for example:
NBIND_CLASS(Reference) { method(reticulateSplines, "reticulate", nbind::Nullable()); method(printString, nbind::Strict()); }
Transferring large chunks of data between languages is fastest using typed arrays or Node.js buffers in JavaScript. Both are accessible from C++ as plain blocks of memory if passed in through the
nbind::Bufferdata type which has the methods:
data()returns an
unsigned char *pointing to a block of memory also seen by JavaScript.
length()returns the length of the block in bytes.
commit()copies data from C++ back to JavaScript (only needed with Emscripten).
This is especially useful for passing
canvas.getContext('2d').getImageData(...).datato C++ and drawing to an on-screen bitmap when targeting Emscripten or Electron.
Example:
#include "nbind/api.h"void range(nbind::Buffer buf) { size_t length = buf.length(); unsigned char *data = buf.data();
if(!data || !length) return;
for(size_t pos = 0; pos < length; ++pos) { data[pos] = pos; }
buf.commit(); }
#include "nbind/nbind.h"
NBIND_GLOBAL() { function(range); }
Example used from JavaScript:
var nbind = require('nbind'); var lib = nbind.init().lib;var data = new Uint8Array(16); lib.range(data);
console.log(data.join(' '));
It prints:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Normally C++ 64-bit integer types are first converted to
doubleand then to JavaScript number which can only hold 53 bits of precision, but it's possible to preserve all bits by using a bignum class. It should have a constructor taking the following arguments:
It should also have a
fromJSfunction which takes a callback, and calls it with those same arguments to pass the data back to C++ when needed.
An example implementation also capable of printing 64-bit numbers to strings in bases 2, 4, 10 and 16 is included.
You can use the
NBIND_ERR("message here");macro to report an error before returning from C++ (
#include "nbind/api.h"first). It will be thrown as an error on the JavaScript side (C++ environments like Emscripten may not support throwing exceptions, but the JavaScript side will).
Make sure your
package.jsonfile has at least the required
emcc-pathand
installscripts:
"scripts": { "emcc-path": "emcc-path","install": "autogypi && node-gyp configure build"
}
The
dependenciessection should have at least:
"dependencies": { "autogypi": "^0.2.2", "nbind": "^0.2.1", "node-gyp": "^3.3.1" }
Your package should also include
binding.gypand
autogypi.jsonfiles.
nbind-example-universal is a good minimal example of compiling a native Node.js addon if possible, and otherwise using a pre-compiled asm.js version.
It has two temporary build directories
build/nativeand
build/asmjs, for compiling both versions.
nbindprovides a binary
copyasmthat can then be used to copy the compiled asm.js library into a nicer location for publishing inside the final npm package.
Note that the native version should be compiled in the
installscript so it runs for all users of the package, and the asm.js version should be compiled in the
prepublishscript so it gets packaged in npm for usage without the Emscripten compiler. See the example
package.jsonfile.
nbind-example-universal is a good minimal example also of calling compiled asm.js code from inside web browsers. The simplest way to get
nbindworking is to add these scripts in your HTML code as seen in the example
index.html:
Make sure to fix the path to
nbind.json the first line if necessary.
nbindhas a fully typed API for interacting with C++ code and it can also automatically generate
.d.tsfiles for your C++ classes and functions. This gives you effortless bindings with compile time type checking for calls from JavaScript to Node.js addons and asm.js modules.
All you have to do is compile your C++ code and run the included
ndtstool to create the type definitions:
npm run -- node-gyp configure build npm run -s -- ndts . > lib-types.d.ts
When run in this way, the first argument of
ndtsis a path from the package root to the
binding.gypfile. Typically the file is in the root so the correct path is
.
Now you can load the C++ code from TypeScript in three different ways. First import
nbind(which also loads the C++ code) and types generated by
ndts:
import * as nbind from 'nbind'; import * as LibTypes from './lib-types';
Then choose your favorite way to initialize it:
Purely synchronous:
const lib = nbind.init().lib;// Use the library.
Asynchronous-aware:
nbind.init((err: any, binding: nbind.Binding) => { const lib = binding.lib;// Use the library. });
Promise-based:
import * as bluebird from 'bluebird';bluebird.promisify(nbind.init)().then((binding: nbind.Binding) => { const lib = binding.lib;
// Use the library. });
Note how there is a type argument
for the init call in all of the examples. It defines types ofbinding.libcontents, which coming from C++ are otherwise unknown to the TypeScript compiler. You can import the types from a file generated by
ndtsor just use to disable typing.
For example if you have a C++ class:
struct C : public A, public B { A *getA();static uint32_t reticulate();
};
And bind it like:
NBIND_CLASS(C) { inherit(A); inherit(B);construct<>(); method(reticulate); getter(getA);
}
ndtswill generate the following typings:
export interface _C extends A, B {} export var _C: { new(): _C };export class C extends _C { /** C(); */ constructor();
/** static uint32_t reticulate(); */ static reticulate(): number; /** A * a; -- Read-only */ a: A;
}
The additional interface
_Cis generated in this case to support multiple inheritance, because
Cextends both
Aand
B.
All the tests are written in TypeScript so if you run:
git clone https://github.com/charto/nbind.git cd nbind npm install npm test
You can then open
test/test.tsin a TypeScript IDE and see the generated typings in action.
nbind generates bindings using C++ templates for compile-time introspection of argument and return types of functions and methods.
Since plain C doesn't have templates, there's no standard way to have a C compiler generate new wrapper code for type conversion and output type information available at run-time.
The easiest way to use nbind with C is to write a C++ wrapper calling the C code, and use nbind with that.
Mapping idiomatic C to JavaScript classes may require some manual work, since it's common to reinvent new ways to do object-oriented programming, usually by using structs as classes and simulating methods by passing struct pointers to functions. C++ classes and methods should be used for these.
A good example is libui-node which uses nbind to generate bindings for libui, mainly a C library.
If you have external library source code, you should compile it separately into a library first, and then link your Node.js addon with it. If the library has an installation script and the addon is only intended for your own use or other users are willing to do some extra steps, it's easiest to install the library globally first.
For best user experience, libui-node is an example of distributing an external library together with your package.
For creating the actual bindings, see for example this and this message and a tutorial for getting the
vglibrary working.
In the browser it can be difficult to stop and debug at the correct spot in optimized C++ code.
nbindprovides an
_nbind_debug()function in
api.hthat you can call from C++ to invoke the browser's debugger when using asm.js.
For debugging a Node.js addon, if you would normally test it like
node test.js, you can instead use
gdb nodeand type
run test.jsin the GDB prompt. Then in case of a crash, it will show where it happened, inspect the stack etc.
You should also modify
nbind.gypi(inside nbind's
srcdirectory) and possibly your own
binding.gyp, to remove any
-O?flags and instead add a
-gflag, then remove the
builddirectory and recompile. This allows GDB to show much more information.
Very similar:
Less similar:
Copyright (c) 2014-2017 BusFaster Ltd