Node.js package class using node-addon-api

Ref: https://github.com/nodejs/node-addon-api#api

Installation

mkdir test &&\
cd test &&\
npm init
sudo npm install -g node-gyp
npm install node-addon-api
<package>
├── binding.gyp
├── index.js
├── node_modules
│   └── node-addon-api
│       └── ...
├── package.json
├── package-lock.json
├── test
│   └── ...
├── .clang-format
└── c_src
    ├── class.cpp
    ├── class.h
    ├── class_wrap.cpp
    ├── class_wrap.h
    └── <target>.cpp

package.json

Ref: https://docs.npmjs.com/files/package.json

{
    "name": "<package>",
...
    "gypfile": true,
...
    "scripts": {
        "install": "node-gyp rebuild",
        "build": "node-gyp rebuild --verbose",
        "clean": "node-gyp clean",
        "cf": "clang-format -style=file -i -verbose c_src/*"
    },
...
    "dependencies": {
        "node-addon-api": "*",
    },
...
}

binding.gyp

Ref: https://github.com/nodejs/node-addon-api/blob/master/doc/setup.md

{
    "targets": [
        {
            "target_name": "<target>",
            "include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
            "dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
            "cflags!": ["-fno-exceptions"],
            "cflags_cc!": ["-fno-exceptions"],
            "cflags": [],
            "cflags_cc": [],
            "defines": [],
            "sources": [
                "c_src/Class.cpp",
                "c_src/Class.h",
                "c_src/ClassWrapper.cpp",
                "c_src/ClassWrapper.h",
                "c_src/<target>.cpp",
            ],
            "libraries": [
                # "/usr/lib/libxxx.so"
            ]
        }
    ]
}

target_name에 '-' 등은 들어갈 수 없습니다.

index.js

module.exports = require("./build/Release/<target>");

Wrapper

Ref: https://medium.com/@atulanand94/beginners-guide-to-writing-nodejs-addons-using-c-and-n-api-node-addon-api-9b3b718a9a7f

c_src/Class.h

#pragma once

class Class
{
public:
    Class( double value );
    double get_value( void );
    double add( double value );

private:
    double m_value;
};

c_src/Class.cpp

#include "Class.h"

Class::Class( double value )
    : m_value( value )
{
}

double Class::get_value( void )
{
    return m_value;
}

double Class::add( double value )
{
    m_value += value;
    return m_value;
}

c_src/ClassWrapper.h

InstanceMethod로 등록 가능한 함수는 아래와 같습니다.

  • typedef void (T::*InstanceVoidMethodCallback)(const CallbackInfo& info);
  • typedef Napi::Value (T::*InstanceMethodCallback)(const CallbackInfo& info);
#pragma once

#include <napi.h>

#include "Class.h"

class ClassWrapper : public Napi::ObjectWrap<ClassWrapper>
{
public:
    static Napi::Object init( Napi::Env env, Napi::Object exports );

    ClassWrapper( const Napi::CallbackInfo &info );

private:
    static Napi::FunctionReference m_constructor;
    Class *                        m_Class;

    Napi::Value get_value( const Napi::CallbackInfo &info );
    Napi::Value add( const Napi::CallbackInfo &info );
};

c_src/ClassWrapper.cpp

#include "ClassWrapper.h"

Napi::FunctionReference ClassWrapper::m_constructor;

Napi::Object ClassWrapper::init( Napi::Env env, Napi::Object exports )
{
    Napi::HandleScope scope( env );

    Napi::Function funcs = DefineClass(
        env,
        "Class",
        {
            InstanceMethod( "get_value", &ClassWrapper::get_value ),
            InstanceMethod( "add", &ClassWrapper::add ),
        } );

    m_constructor = Napi::Persistent( funcs );
    m_constructor.SuppressDestruct();

    exports.Set( "Class", funcs );
    return exports;
}

ClassWrapper::ClassWrapper( const Napi::CallbackInfo &info )
    : Napi::ObjectWrap<ClassWrapper>( info )
{
    Napi::Env         env = info.Env();
    Napi::HandleScope scope( env );

    if( info.Length() < 1 || !info[0].IsNumber() )
    {
        Napi::TypeError::New( env, "Arguments must be (value)." )
            .ThrowAsJavaScriptException();
    }

    double value = info[0].As<Napi::Number>();

    m_Class = new Class( value );
}

Napi::Value ClassWrapper::get_value( const Napi::CallbackInfo &info )
{
    Napi::Env         env = info.Env();
    Napi::HandleScope scope( env );

    return Napi::Number::New( env, m_Class->get_value() );
}

Napi::Value ClassWrapper::add( const Napi::CallbackInfo &info )
{
    Napi::Env         env = info.Env();
    Napi::HandleScope scope( env );

    if( info.Length() < 1 || !info[0].IsNumber() )
    {
        Napi::TypeError::New( env, "Arguments must be (value)." )
            .ThrowAsJavaScriptException();
    }

    double value = info[0].As<Napi::Number>();

    return Napi::Number::New( env, m_Class->add( value ) );
}

c_src/<target>.cpp

#include <napi.h>

#include "ClassWrapper.h"

Napi::Object init_all( Napi::Env env, Napi::Object exports )
{
    return ClassWrapper::init( env, exports );
}

NODE_API_MODULE( NODE_GYP_MODULE_NAME, init_all )

Build

npm run build

test/test.js

const addon = require("../build/Release/<target>");

console.log("wrapper info", addon);

const testClass = new addon.Class(10);

console.log(testClass.get_value());
console.log(testClass.add(2));
node test/test.js
wrapper info { Class: [Function: Class] }
10
12