c++node.jsnode.js-addon

Link Node.js C++ add-on with static library


I have a C++ project that is intended to perform some calculations. The C++ code will sit on a server that as back end to a browser based GUI. Node.js seems to be suitable for this job. I've gone through the tutorials and learnt to build C++ code to be used as a nodejs module.

For the sake of simplicity, I'd like to compile the C++ code as a static library. I could then write a C++ class which references this library which can then be used in the nodejs environment. The point of this is so I don't have to use node-gyp build to build my entire C++ project. This way I can further develop the C++ code without worrying too much about the front end.

To achieve this, I have done the following:

  1. Built a simple C++ library as follows. Building this in Visual Studio 2013 to get the .lib file.

    //MyClass.h
    #pragma once
    class MyClass
    {
    public:
        double Multiply(double a, double b)
        {
            return a*b;
        }
        MyClass();
        ~MyClass();
    };
    
  2. Created the C++ object to be used as a nodejs module like so:

    // myobject.h
    #ifndef MYOBJECT_H
    #define MYOBJECT_H
    
    #include <node.h>
    #include <node_object_wrap.h>
    #include "MyLib\MyClass.h"
    
    namespace demo {
    
        class MyObject : public node::ObjectWrap {
        public:
            static void Init(v8::Local<v8::Object> exports);
            static MyClass* mycalcobj;
    
        private:
            explicit MyObject(double value = 0);
            ~MyObject();
    
            static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
            static void PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args);
            static v8::Persistent<v8::Function> constructor;
            double value_;
    
    
        };
    
    }  // namespace demo
    
    #endif
    

    and the cpp file

        // myobject.cpp
        #include "myobject.h"
    
        //#include "worker.h"
        namespace demo {
    
            using v8::Function;
            using v8::FunctionCallbackInfo;
            using v8::FunctionTemplate;
            using v8::Isolate;
            using v8::Local;                 
            using v8::Number;
            using v8::Object;
            using v8::Persistent;
            using v8::String;
            using v8::Value;
    
            Persistent<Function> MyObject::constructor;
    
            MyObject::MyObject(double value) : value_(value) {
            }
    
            MyObject::~MyObject() {
            }
    
            void MyObject::Init(Local<Object> exports) {
                Isolate* isolate = exports->GetIsolate();
    
                // Prepare constructor template
                Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);
                tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject"));
                tpl->InstanceTemplate()->SetInternalFieldCount(1);
    
                // Prototype
                NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne);
    
                constructor.Reset(isolate, tpl->GetFunction());
                exports->Set(String::NewFromUtf8(isolate, "MyObject"),
                    tpl->GetFunction());
            }
    
            void MyObject::New(const FunctionCallbackInfo<Value>& args) {
                Isolate* isolate = args.GetIsolate();
    
                if (args.IsConstructCall()) {
                    // Invoked as constructor: `new MyObject(...)`
                    double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue();
                    MyObject* obj = new MyObject(value);
                    obj->Wrap(args.This());
                    args.GetReturnValue().Set(args.This());
                }
                else {
                    // Invoked as plain function `MyObject(...)`, turn into construct call.
                    const int argc = 1;
                    Local<Value> argv[argc] = { args[0] };
                    Local<Function> cons = Local<Function>::New(isolate, constructor);
                    args.GetReturnValue().Set(cons->NewInstance(argc, argv));
                }
            }
    
    
            //define functions here
            void MyObject::PlusOne(const FunctionCallbackInfo<Value>& args) {
                Isolate* isolate = args.GetIsolate();
                MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder());
                double x = obj->value_;
                mycalcobj = new MyClass();
    
                x=mycalcobj->Multiply(2, x);
    
                obj->value_= x;
    
                args.GetReturnValue().Set(Number::New(isolate, obj->value_));
            }
    
        }  // namespace demo
    
  3. Create the main cpp file to initialise

        #include <node.h>
        #include "myobject.h"
    
        namespace demo {
    
            using v8::Local;
            using v8::Object;
    
            void InitAll(Local<Object> exports) {
                MyObject::Init(exports);
            }
    
            NODE_MODULE(addon, InitAll)
    
        }  // namespace demo
    
  4. The binding.gyp file is defined as:

        {
          "targets": [
            {
              "target_name": "cpphello",
              "sources": [ "cpphello.cpp", "myobject.cpp"],
              "libraries": [ "D:/East101/Adri/javascript/socketio/cpplib/build/x64/Debug/MyLib"]
            }
          ]
        }
    
  5. Build the project using node-gyp configure build

I get the following messages:

    MyLib.lib(MyClass.obj) : warning LNK4075: ignoring '/EDITANDCONTINUE' due to '/OPT:ICF' specification [D:\East101\Adri\javascript\socketio\cpplib\build\cpphello.vcxproj]


    LINK : warning LNK4098: defaultlib 'MSVCRTD' conflicts with use of other libs;use /NODEFAULTLIB:library [D:\East101\Adri\javascript\socketio\cpplib\build\cpphello.vcxproj]

    myobject.obj : error LNK2001: unresolved external symbol "public: static class MyClass * demo::MyObject::mycalcobj" (?mycalcobj@MyObject@demo@@2PEAVMyClass@@EA) [D:\East101\Adri\javascript\socketio\cpplib\build\cpphello.vcxproj] D:\East101\Adri\javascript\socketio\cpplib\build\Release\cpphello.node : fatal error LNK1120: 1 unresolved externals [D:East101\Adri\javascript\socketio\cpplib\build\cpphello.vcxproj]

I need some assistance to either fix this error and make it work as I envision it or let me know if there's a better way. I'm quite new to C++ and coding in general so I may just be approaching this wrong.


Solution

  • C++ is a Little bit different than Java etc... If you declare a static member, you Need to define it in the CPP file. Think like this: a normal member variable is allocated when you instantiate an object. But a static member variable exists from the beginning. So you Need to define it somewhere to actually allocate Memory for it.

    You can resolve it if you put the following in your cpp file:

    namespace demo
    {
    
        MyClass* MyObject::mycalcobj;
    
        //...
    

    (e.g. have a look here or here)