Skip to main content

Class binding with node-addon-api


Setup

<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_wrapper.cpp
├── class_wrapper.h
└── <target>.cpp

binding.gyp

binding.gyp
{
"targets": [
{
# ...

"sources": [
"c_src/class.cpp",
"c_src/class.h",
"c_src/class_wrapper.cpp",
"c_src/class_wrapper.h",
"c_src/<target>.cpp",
],

#...
}
]
}

Wrapper

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;
}

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

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

#include "class.h"

#include <napi.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/class_wrapper.cpp
#include "class_wrapper.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 "class_wrapper.h"

#include <napi.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/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

Reference