Back to Napajs

Plus Number

examples/modules/plus-number/README.md

0.2.37.7 KB
Original Source

Plus Number

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();

Wrapped class

plus-number.h declares the class with one constructor and one method, Add().

h
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

Wrapper class

plus-number-wrap.h declares the wrapper class inherited from NAPA_OBJECTWRAP as follows,

h
#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,

cpp
#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,

cpp
#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);

Transition from node.js module

  • NAPA_OBJECTWRAP is equivalent to node::ObjectWrap, so object's lifetime works as Javascript object.
  • One big difference is how to handle the persistent constructor. While Node.js has only one thread for Javascript and static constructor instance is fine, Napa should support one constructor instance per V8 isolate. These macros help constructor instance creation.
    • NAPA_DECLARE_PERSISTENT_CONSTRUCTOR declares constructor instance.
    • NAPA_DEFINE_PERSISTENT_CONSTRUCTOR defines constructor instance.
    • NAPA_SET_PERSISTENT_CONSTRUCTOR makes a local constructor as persistent. Napa stores it into thread local storage to allow one instance per V8 isolate.
    • NAPA_GET_PERSISTENT_CONSTRUCTOR return stored constructor instance.
  • NAPA_SET_PROTOTYPE_METHOD is equivalent to NODE_SET_PROTOTYPE_METHOD, which add a prototype method to Javascript object.

Typescript

plus-number.ts doesn't need to fully implement PlusNumber.add() since the signature of PlusNumber instance returned by addon.createPlusNumber() is the same.

plus-number.ts

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

plus-number.d.ts

d.ts
export declare class PlusNumber {
    add(value: number): number;
}
export declare function createPlusNumber(value?: number): PlusNumber;

NPM Package

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.

Mocha test

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