Search code examples
c++node.js64-bitswigarm64

dyld[49745] missing symbol called using c++ swig with node.js by calling a function out of the library


I try to create an example using SWIG and NodeJS on my M1 (arm64) Mac, but I want to mention this as early as possible: this issue appears also on an Intel (x64) Mac.

I create my simple example Files like this:
example.h

#pragma once

class Die
{
public:
    Die();
    ~Die();
    int foo(int a);
};

Die* getDie();

//to test if the issue also appears getting a simple functiom without any class context.
extern "C" 
{
    bool getFoo();
}

here is the implementation.

example.cpp

#include <iostream>
#include "example.h"
int Die::foo(int a) 
{
    std::cout << "foo: running fact from simple_ex" << std::endl;
    return 1;
}

Die::Die()
{

}

Die::~Die()
{

}

// out of Class Context
Die* getDie() 
{
    return new Die();
}

extern "C" 
{

    bool getFoo() 
    {
        return true;
    }

}

my Swig interface is as follows:

example.i

%module example
%{
#include "example.h"
%}
%include "example.h"

then i create my example_warp.cxx file. But as actually the 4.0.2 Version of Swig is not compatible with NodeJs v16.0.0 (read SWIG support for NodeJS v12 #1520 and Prepare SWIG for Node.js v12 #1746).

Therefore i needed to build swig from source using master branch with the current version (4.1.0). Please keep that in mind.

Swig Command:

swig -Wall -c++ -javascript -node example.i

Here now some files preparing to create the .node file

package.json { "name": "SwigJS", "version": "0.0.1", "scripts": { "start": "node index.js", "install": "node-gyp clean configure build"

    },
    "dependencies": {
      "nan": "^2.16.0",
      "node-gyp": "^9.0.0"
    },
    "devDependencies": {
      "electron-rebuild": "^3.2.7"
    }
}

the package.json i got from a mate as an example an edited it to work with my project so there may be some lines not really needed by me.

binding.gyp

{
  "targets": [
    {
      "target_name": "SwigJS",
      "sources": [ "example_wrap.cxx" ],
      "include_dirs" : [ "<!(node -e \"require('nan')\")" ]
    }
  ]
}

now i build my SwigJS.node file using:

node-gyp configure
node-gyp build

it runs through without any errors.

Now i try to access the node-file in my JavaScript but i always get the error message:

missing symbol called

index.js

const Swigjs = require("./build/Release/SwigJS.node");

console.log("exports :", Swigjs); //show exports


die = Swigjs.getDie(); //try to get the Class

console.log(die.foo(5)); //call a function from the class

the output looks like this:

[Running] node "/Users/rolf/Documents/SwigJS/index.js"
exports : {
  getDie: [Function (anonymous)],
  getFoo: [Function (anonymous)],
  Die: [Function: Die]
}
dyld[49745]: missing symbol called

[Done] exited with code=null in 0.12 seconds

What i have tried to find the error:

  • tried to build the .node file on an x64 architecture to check if it is an arm topic with NodeJS v16:17 on an Intel x64 Mac from a mate.
  • installed NodeJS 16.0.0 (first version supporting arm64 on mac)
  • as the Github Issues suggest NodeJS version 12 tried to build and run this on an x64 Intel Mac with NodeJS v12.13.0
  • tried to force x64 architecture leading to different error but because of incompatible library (x64) using a arm64 mac

all of it (except last mentioned) ended with the same result "missing symbol called"

help would be appreciated big.


Solution

  • Your question is an interesting take on a FAQ on this site: What is an undefined reference/unresolved external symbol error and how do I fix it?.

    The role of SWIG is indeed to generate glue code between Node.js and C++ code. But it does only that. If you inspect the .dylib file that is associated with your NodeJS module using the nm command, you will see that it has Undefined references to the C++ functions it wraps.

    This is by design. SWIG expects that the code it wraps is somehow already loaded into memory. There are three approaches to do so:

    • Compile example.cpp directly into the SWIG wrapper. Or compile it to a static library first (example.a) and link that statically into the wrapper. I think it suffices to add example.cpp to the sources section of binding.gyp
    • Compile example.cpp into a library (example.dylib) and dynamically link it to the SWIG wrapper. I have not used GYP myself yet, but I think it means adding the following to your targets entry in bindings.gyp:
      'link_settings': {
        'libraries': [
          '-lexample',
        ],
      },
    
    • Compile example.cpp into a library (example.dylib) and use dlopen to load it explicitly. This puts a tremendous burden on your users and is very hard to debug. Do not do this.