examples/modules/plus-number/README.md
This example shows the napa module, which wraps C++ objects/classes. Instead of using Javascript new operator, it uses a factory pattern. i.e.
var obj = addon.createPlusNumber();
// instead of
// var obj = new addon.PlusNumber();
plus-number.h declares the class with one constructor and one method, Add().
namespace napa {
namespace demo {
/// <summary> Example class to show how to create Napa module using a wrapped C++ class. </summary>
class PlusNumber {
public:
/// <summary> Constructor with initial value. </summary>
explicit PlusNumber(double value = 0.0);
/// <summary> Add the given value and return the result. </summary>
double Add(double value);
private:
double _value;
};
} // namespace demo
} // namespace napa
plus-number-wrap.h declares the wrapper class inherited from NAPA_OBJECTWRAP as follows,
#include <napa/module.h>
#include <plus-number.h>
namespace napa {
namespace demo {
/// <summary> Napa example module wrapping PlusNumber class. </summary>
class PlusNumberWrap : public NAPA_OBJECTWRAP {
public:
/// <summary> Register this class into V8. </summary>
static void Init();
/// <summary> Enable to create an instance by createPlusNumber() Javascript API. </summary>
/// <param name="args"> Addend as PlusNumber constructor parameter. </param>
static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args);
private:
/// <summary> Exported class name. </summary>
static const char* _exportName;
/// <summary> Constructor with initial value. </summary>
explicit PlusNumberWrap(double value = 0.0);
/// <summary> Create PlusNumber instance at V8. </summary>
/// <param name="args"> Addend as PlusNumber constructor parameter. </param>
static void NewCallback(const v8::FunctionCallbackInfo<v8::Value>& args);
/// <summary> Add value. </summary>
/// <param name="args"> Addend. </param>
static void Add(const v8::FunctionCallbackInfo<v8::Value>& args);
/// <summary> Declare persistent constructor to create PlusNumber instance. </summary>
/// <remarks> Napa creates persistent constructor at each isolate while node.js creates the static instance. </remarks>
NAPA_DECLARE_PERSISTENT_CONSTRUCTOR();
PlusNumber _plusNumber;
};
} // namespace demo
} // namespace napa
plus-number-wrap.cpp implements each functions as follows,
#include "plus-number-wrap.h"
using namespace napa::demo;
using namespace v8;
const char* PlusNumberWrap::_exportName = "PlusNumberWrap";
// Define persistent constructor.
NAPA_DEFINE_PERSISTENT_CONSTRUCTOR(PlusNumberWrap);
PlusNumberWrap::PlusNumberWrap(double value)
: _plusNumber(value) {
}
void PlusNumberWrap::Init() {
auto isolate = Isolate::GetCurrent();
// Prepare constructor template.
auto functionTemplate = FunctionTemplate::New(isolate, NewCallback);
functionTemplate->SetClassName(String::NewFromUtf8(isolate, _exportName));
functionTemplate->InstanceTemplate()->SetInternalFieldCount(1);
// Set prototype method.
NAPA_SET_PROTOTYPE_METHOD(functionTemplate, "add", Add);
// Set persistent constructor into V8.
NAPA_SET_PERSISTENT_CONSTRUCTOR(_exportName, functionTemplate->GetFunction());
}
void PlusNumberWrap::NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args) {
auto isolate = args.GetIsolate();
HandleScope scope(isolate);
const int argc = 1;
Local<Value> argv[argc] = { args[0] };
auto constructor = NAPA_GET_PERSISTENT_CONSTRUCTOR(_exportName, PlusNumberWrap);
auto context = isolate->GetCurrentContext();
auto instance = constructor->NewInstance(context, argc, argv).ToLocalChecked();
args.GetReturnValue().Set(instance);
}
void PlusNumberWrap::NewCallback(const FunctionCallbackInfo<Value>& args) {
auto isolate = args.GetIsolate();
HandleScope scope(isolate);
CHECK_ARG(isolate,
args.IsConstructCall(),
"PlusNumberWrap instance must be created by the factory.");
CHECK_ARG(isolate,
args.Length() == 0 || args.Length() == 1,
"Only one or no argument is allowed.");
if (args.Length() == 1) {
CHECK_ARG(isolate,
args[0]->IsNumber(),
"The first argument must be a number.");
}
double value = args[0]->IsUndefined() ? 0.0 : args[0]->NumberValue();
auto wrap = new PlusNumberWrap(value);
wrap->Wrap(args.This());
args.GetReturnValue().Set(args.This());
}
void PlusNumberWrap::Add(const FunctionCallbackInfo<Value>& args) {
auto isolate = args.GetIsolate();
HandleScope scope(isolate);
CHECK_ARG(isolate,
args.Length() == 1 && args[0]->IsNumber(),
"Number must be given as argument.");
auto wrap = NAPA_OBJECTWRAP::Unwrap<PlusNumberWrap>(args.Holder());
auto value = wrap->_plusNumber.Add(args[0]->NumberValue());
args.GetReturnValue().Set(Number::New(isolate, value));
}
addon.cpp implements the addon as below,
#include "plus-number-wrap.h"
using namespace napa::demo;
using namespace v8;
void CreatePlusNumber(const FunctionCallbackInfo<Value>& args) {
PlusNumberWrap::NewInstance(args);
}
void InitAll(Local<Object> exports) {
PlusNumberWrap::Init();
NAPA_SET_METHOD(exports, "createPlusNumber", CreatePlusNumber);
}
NAPA_MODULE(addon, InitAll);
plus-number.ts doesn't need to fully implement PlusNumber.add() since the signature of PlusNumber instance returned by addon.createPlusNumber() is the same.
var addon = require('../bin/addon');
export declare class PlusNumber {
public add(value: number): number;
}
export function createPlusNumber(value: number = 0): PlusNumber {
return addon.createPlusNumber(value);
}
export declare class PlusNumber {
add(value: number): number;
}
export declare function createPlusNumber(value?: number): PlusNumber;
NPM package contains the additional binary plus-number.dll, which is the shared library for PlusNumber class. It's placed at bin directory, so either Node.js or Napa can resolve the shared object path in the same way as it does for a module.
var assert = require('assert');
var plusNumber = require('plus-number');
describe('Test suite for plus-number', function () {
it('adds a given value', function () {
var po = plusNumber.createPlusNumber(3);
var result = po.add(4);
assert.equal(result, 7);
});
it('fails with constructor call', function () {
var failed = false;
try {
var po = new plusNumber.PlusNumber();
}
catch (error) {
failed = true;
}
assert.equal(failed, true);
});
});