Using SWIG with NodeJS
Contents
Using SWIG with NodeJS¶
Caution¶
Complete documentation is available on the SWIG website (at time of writing, using SWIG 4.0.2), however I am adding notes for the current state and most likely the anticipated future state of using SWIG with NodeJS: versions are very important.
Any familiarity with SWIG lends itself to Javascript perfectly, and indeed if using Javascript Core
swig -javascript -jsc [inteface].i
then there is no problem on NodeJS 14. However, when trying to build with -node
or -v8
, the SWIG generated wrapper will fail with errors along the lines of:
...
../example_wrap.cxx:1277:44: error: expected '(' for function-style cast or type construction
int SwigV8Packed_Check(v8::Handle<v8::Value> valRef) {
~~~~~~~~~^
../example_wrap.cxx:1277:28: error: no member named 'Handle' in namespace 'v8'
int SwigV8Packed_Check(v8::Handle<v8::Value> valRef) {
~~~~^
../example_wrap.cxx:1277:46: error: use of undeclared identifier 'valRef'
int SwigV8Packed_Check(v8::Handle<v8::Value> valRef) {
^
../example_wrap.cxx:1277:53: error: expected ';' after top level declarator
int SwigV8Packed_Check(v8::Handle<v8::Value> valRef) {
^
;
../example_wrap.cxx:1472:17: error: no template named 'Handle' in namespace 'v8'
SWIGRUNTIME v8::Handle<v8::FunctionTemplate> SWIGV8_CreateClassTemplate(const char* symbol) {
~~~~^
../example_wrap.cxx:1476:31: error: no viable conversion from 'MaybeLocal<v8::String>' to 'Local<v8::String>'
class_templ->SetClassName(SWIGV8_SYMBOL_NEW(symbol));
^~~~~~~~~~~~~~~~~~~~~~~~~
../example_wrap.cxx:827:32: note: expanded from macro 'SWIGV8_SYMBOL_NEW'
#define SWIGV8_SYMBOL_NEW(sym) v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), sym)
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
...
Keeping a close eye on the SWIG GitHub Repository (specifically the issues), it can be a little confusing to discern exactly what the fix for this is, and arguably the documentation is a additionally ambiguous when it says
The V8 code that SWIG generates should work with most versions from 3.11.10 up to 3.29.14 and later.
One would expect then forwards compatibility, however this is not the case and will fail with V8 for NodeJS 12 and 14. This GitHub issue discusses the problems, and a recent pull discusses how a solution surrounding SWIG_Object
is making development difficult.
So what is the solution? Well… backdate to NodeJS 10 with
$ node -p process.versions.v8
# 6.8.275.32-node.58
OSX On OSX, with
brew
, it is easy to maintain two (or more) different NodeJS versions using the the linking commands; we install
brew install node node@10
Unlink the version we do not desire
brew unlink node
and link the one we do
brew link node@10
Note: for NodeJS 10, this may also require running
echo 'export PATH="/usr/local/opt/node@10/bin:$PATH"' >> ~/.zshrc
which would have to be commented out when unlinking. There’s probably an elegant way of automating this when brew link/unlink
is invoked, but I am yet to properly explore brew
internals in order to devise how.
Example¶
As usual with SWIG, we have our .cpp
and .hpp
files (note that pure C is not compatible with V8, since it is a C++ API), and define an interface file .i
along the lines of
%module example
%{
#include "example.hpp"
%}
%include "example.hpp"
SWIG with NodeJS requires no additional include statements (CF: SWIG with Python). We can process and generate the wrapper code with
swig -c++ -javascript -node example.i
To compile the wrapper into a Node module, we may use the node-gyp
build tool, which we can install with
npm i --save node-gyp
If not installing globally, the executable is located in
node_modules/node-gyp/bin/node-gyp.js
and may be worth aliasing in the package.json
, although once configured, a call to npm i
will invoke the rebuild
command of node-gyp
.
To configure node-gyp
, we define a binding.gyp
file at the root of our project, with the contents, e.g.
{
"targets": [
{
"target_name": "example",
"sources": [ "example.cxx", "example_wrap.cxx" ]
}
]
}
We then compile the module with
node-gyp configure build
The configure
command is only required once, or when making changes to the environment.
Now, the module may be used, by importing it with
const example = require("./build/Release/example");
Resources¶
Useful resources to bare in mind are
the
gyp
user documentation for definingbindings.gyp
a Hello World example for using
node-gyp