Dependencies between C/C++ Addons in Node.js

Should you ever be writing a C/C++ addon in Node.js which depends on another module that is also a C/C++ addon, I have one piece of advice: keep your dependencies at the JavaScript level and avoid any linkage between the native addons. In most cases, sticking to the JavaScript interface will be the most obvious thing to do. If, however, you're tempted to do a little optimization or use some shorthand by referencing the classes or functions in another module, my advice is don't.

Some languages provide facilities for that kind of dependency. PHP jumps to mind as the obvious example. The DOM and XSL extensions in PHP are just two of several that depend on the base libxml extension. Those extensions can be installed separately, but they share dependences at the C language level with libxml. PHP handles that kind of linkage. Node.js does not.

I should note that this word of caution does not apply to modules like nan, which do not actually provide external classes or functions you must link to. The nan module is just a header file you include.

What Would Happen If I Did Try to Use Classes in Another Addon?

The short answer is that you'd have a lot of linkage issues to solve yourself. If you were developing on a Mac, you'd find that if you put the headers you depend upon in your include path, and required the module you depend on before loading your own bindings, things would probably work. That would not work at all on Linux, however, and you'd probably be left scratching your head as to why.

The reason for the difference between systems turns out to be a quirk in the standard dlopen function. C/C++ addons are just dynamic libraries, and Node loads those libraries with a function in libuv called uv_dlopen. That provides a uniform interface for loading dynamic libraries on both Windows and Unix-like systems. For Unix-style systems libuv turns that into this call:

lib->handle = dlopen(filename, RTLD_LAZY);

The only flag being passed to dlopen is RTLD_LAZY. Without any other flags specified, dlopen on OSX will behave as though RTLD_GLOBAL were given by default. Linux, on the other hand, will default to the behavior of RTLD_LOCAL. That means you get completely opposite behavior on different systems. On OSX, the symbols in loaded libraries will be pulled in to the global namespace. In other words, the functions in the loaded libraries can be found and called by other libraries that are loaded. On Linux, those functions can essentially only be called from within the same library. Off hand, I'm not sure how Windows might further complicate the picture. LoadLibraryEx, which libuv uses, doesn't appear to provide flags similar to RTLD_LOCAL or RTLD_GLOBAL, so it may just always put symbols in the global space, but that would just be a guess.

Alterntively, you could try to to include the other addon as an explicit dependency in your binding.gyp. That will build a copy of the library in your local build directory, but, as far as I can tell, it won't create a link to it in your own shared library.

That leaves you with very few desirable options. The most straightforward might be to explicitly load the library from the other module with the right flags. Assuming you wouldn't like to put together your own loader in C, the easiest approach in JavaScript would be to use the ffi module to load the other library before your own bindings. That would look something like:

var DynamicLibrary = require('ffi').DynamicLibrary;

new DynamicLibrary(
  '/path/to/some/dependency',
  DynamicLibrary.FLAGS.RTLD_LAZY | DynamicLibrary.FLAGS.RTLD_GLOBAL
);

That code is going to be a bit brittle, however. It doesn't handle all of the different variations in binding output locations and naming conventions that are in the wild, and any change in the build behavior in Node would require changes to your code as well.

Overall, it just doesn't feel like a good solution for a production system. So, as I said at the beginning, my recommendation is to just avoid the problem altogether.