Building Mixed C and C++ node.js Modules

I had some trouble getting node.js modules containing both C and C++ files to build. It took me some time to track down the problem, and the solution isn't especially obvious, so I'm posting this on the web in case anyone else runs into this.

The Situation

We've got an internal development tool at SSDCache that requires a web interface and calls into a user-space version of our core driver code. node.js has turned out to be a pretty good choice for this as it's got pretty convenient C++ bindings, and we like JavaScript as a language.

The standard way to connect to node.js from C++ is via a module, which you typically build using the "node-waf" script that ships with node, and which is essentially a wrapper for for the waf build system. It's easy to get your C++ code compiling using the example wscript build file.

However, most of our driver is written in pure C, and many source files have turned out not to be valid C++. In most cases, this is easily fixed by inserting explicit static casts where we were casting from void* implicitly, but in some cases it's a little trickier. Instead of spending time picking apart perfectly good code, I decided to try to get waf to build our .c files with gcc, not g++. This turned out to be somewhat trickier than expected.

Attacking the Problem

The typical wscript file contains a bunch of settings with "cxx" in the name, so I started by adding "c" versions, which didn't work. Changing them to "cc" (for "C compiler") got rid of the errors:

def set_options(opt):
  opt.tool_options("compiler_cxx")
  opt.tool_options("compiler_cc")

def configure(conf):
  conf.check_tool("compiler_cc")
  conf.check_tool("compiler_cxx")
  conf.check_tool("node_addon")

A node-waf configure later, the files were still being compiled with g++. This line seemed the next obvious candidate:

  obj = bld.new_task_gen("cxx", "shlib", "node_addon")

Unfortunately, simply inserting a strategically placed "cc" didn't quite have the intended effect. The .c files were now being compiled correctly, but the "shlib" task was suddenly no recognised:

feature 'shlib' does not exist - bind at least one method to it

Node modules are essentially dynamically linked (shared) libraries, and the shlib task links your object (.o) files into such a library.

After some frustrated experimentation, I finally discovered a report filed in the waf bug tracker by someone having a similar problem. It turns out you have specify both cxxshlib and cshlib to make it work. Waf also seems fussy about the order the features appear in.

Finally, if you're specifying any of your own compiler flags in cxxflags, those won't be used for the C compiler - that uses cflags.

The final wscript

A mixed C and C++ node.js module example build script therefore looks something like this:

srcdir = "."
blddir = "build"
VERSION = "0.0.1"

def set_options(opt):
  opt.tool_options("compiler_cc")
  opt.tool_options("compiler_cxx")

def configure(conf):
  conf.check_tool("compiler_cc")
  conf.check_tool("compiler_cxx")
  conf.check_tool("node_addon")

def build(bld):
  obj = bld.new_task_gen("cc", "cxx", "cxxshlib", "cshlib", "node_addon")
  obj.cxxflags = ["-g", "-D_FILE_OFFSET_BITS=64", "-D_LARGEFILE_SOURCE"]
  obj.cflags = ["-g", "-D_FILE_OFFSET_BITS=64", "-D_LARGEFILE_SOURCE"]
  obj.target = "example"
  obj.source = "example_cpp_file.cpp"
  obj.source = "example_c_file.c"

Footnotes

The waf bug tracker link I found uses the build task "c", not "cc". If you're on a different OS (we're currently only using our tool on Mac OS X), and you're having trouble, you might want to try that instead.

Finally, a while back, I made a helper for automating some of the binding glue for converting between JavaScript and C++ types on calls from JS to C++ class methods. You give it a C++ method to export to JavaScript and it detects the types of the arguments automatically, so you don't need to bother writing your own null checks and unboxing. Likewise, you can return a value with a C++ type from the method and it will be converted to the corresponding JS type for you. You may find it useful, we're certainly using it heavily here.