Node-addon-api: Example request on function reference/callback

Created on 6 Aug 2019  路  8Comments  路  Source: nodejs/node-addon-api

Hi,

I'm working on a project where I need to be capable of calling a javascript function whenever I need, from c++ code sending commands (or events) to my node server. I believe I can achieve that by storing a function reference using the corresponding napi class and I'm trying to write a prototype. Fact is, this seems rather complex based only on the current examples, so I'd like to kindly ask you (and suggest too) to share an example project including the following capabilities:

  • Implement a named function in js;
  • Pass it to an addon;
  • Store the js function in the addon;
  • Implement an exported function in the addon. This function calls a pure c++ function (without parameters) that calls the function stored in the previous item.

Its a suggestion, I think it would make things much more clear regarding the Reference class and callbacks. Appreciate any comments too!

Most helpful comment

HI @felipezacani,
you can use Napi::FuntionReference to store the JavaScript function. I report a little example below:

index.js

'use strict'

const { NativeAddon } = require('bindings')('addon')

function JSFunction() {
    console.log('Hi I\'m a JS function!')
}

const nf = new NativeAddon(JSFunction)

nf.callInNativeFunction()

native-addon.h

#include <napi.h>

class NativeAddon : public Napi::ObjectWrap<NativeAddon> {
    public:
        static Napi::Object Init(Napi::Env env, Napi::Object exports);
        NativeAddon(const Napi::CallbackInfo& info);

    private:
        static Napi::FunctionReference constructor;
        Napi::FunctionReference js;
        Napi::Value CallInNativeFunction(const Napi::CallbackInfo& info);
};

native-addon.cc

#include "native-addon.h"

Napi::FunctionReference NativeAddon::constructor;

Napi::Object NativeAddon::Init(Napi::Env env, Napi::Object exports) {
  Napi::HandleScope scope(env);

  Napi::Function func = DefineClass(env, "NativeAddon", {
    InstanceMethod("callInNativeFunction", &NativeAddon::CallInNativeFunction)
  });

  constructor = Napi::Persistent(func);
  constructor.SuppressDestruct();

  exports.Set("NativeAddon", func);
  return exports;
}

NativeAddon::NativeAddon(const Napi::CallbackInfo& info) 
: Napi::ObjectWrap<NativeAddon>(info)  {
  js = Napi::Persistent(info[0].As<Napi::Function>());
}

Napi::Value NativeAddon::CallInNativeFunction(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();
    // Call your native C / C++ method
    // ...
    // Call in JS
    js.Call({});

    return env.Undefined();
}

I hope that he example is helpful to you in general when you want store JavaScript Object or Function you need to use Napi::ObjectReferenceand [Napi::FunctionReference`](https://github.com/nodejs/node-addon-api/blob/master/doc/function_reference.md).
Here I post the complete example:
ref-example.zip

All 8 comments

HI @felipezacani,
you can use Napi::FuntionReference to store the JavaScript function. I report a little example below:

index.js

'use strict'

const { NativeAddon } = require('bindings')('addon')

function JSFunction() {
    console.log('Hi I\'m a JS function!')
}

const nf = new NativeAddon(JSFunction)

nf.callInNativeFunction()

native-addon.h

#include <napi.h>

class NativeAddon : public Napi::ObjectWrap<NativeAddon> {
    public:
        static Napi::Object Init(Napi::Env env, Napi::Object exports);
        NativeAddon(const Napi::CallbackInfo& info);

    private:
        static Napi::FunctionReference constructor;
        Napi::FunctionReference js;
        Napi::Value CallInNativeFunction(const Napi::CallbackInfo& info);
};

native-addon.cc

#include "native-addon.h"

Napi::FunctionReference NativeAddon::constructor;

Napi::Object NativeAddon::Init(Napi::Env env, Napi::Object exports) {
  Napi::HandleScope scope(env);

  Napi::Function func = DefineClass(env, "NativeAddon", {
    InstanceMethod("callInNativeFunction", &NativeAddon::CallInNativeFunction)
  });

  constructor = Napi::Persistent(func);
  constructor.SuppressDestruct();

  exports.Set("NativeAddon", func);
  return exports;
}

NativeAddon::NativeAddon(const Napi::CallbackInfo& info) 
: Napi::ObjectWrap<NativeAddon>(info)  {
  js = Napi::Persistent(info[0].As<Napi::Function>());
}

Napi::Value NativeAddon::CallInNativeFunction(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();
    // Call your native C / C++ method
    // ...
    // Call in JS
    js.Call({});

    return env.Undefined();
}

I hope that he example is helpful to you in general when you want store JavaScript Object or Function you need to use Napi::ObjectReferenceand [Napi::FunctionReference`](https://github.com/nodejs/node-addon-api/blob/master/doc/function_reference.md).
Here I post the complete example:
ref-example.zip

@felipezacani Any news about this issue? Do you need some help?

@felipezacani Any news about this issue? Do you need some help?

Thanks!

Yes, your example was very helpful, it gave me a good insight on improving my code with OO. The real "trick" I missed was actually using the function reference's own ENV to call itself (I think that's missing in your js.Call({})).

Hi @felipezacani,
your environment (this) is stored by Napi::FunctionReference and when you call the method Napi::FunctionReference::Call using only empty initialiser list the call happen using the stored environment.
If you need to specify a different this object you can use the other methods of Napi::FunctionReference:

Napi::Value Napi::FunctionReference::Call(napi_value recv, const std::initializer_list<napi_value>& args) const;
Napi::Value Napi::FunctionReference::Call(napi_value recv, const std::vector<napi_value>& args) const;
Napi::Value Napi::FunctionReference::Call(napi_value recv, size_t argc, const napi_value* args) const;

The napi_value recv represents this object passed to the referenced function when it's called.

The napi_value recv represents this object passed to the referenced function when it's called.

Good to know. I was using ref->env as the first parameter for every call, because I saw it being used like that in another issue.

So, lets say you just write a script initializing an addon and nothing else, the stored env will always be the global environment, right?

In my case, I initialize the addon along with all global things in my server (and I provide it with a global callback function).

So, lets say you just write a script initializing an addon and nothing else, the stored env will always be the global environment, right?

Yes you're right.

Thank you @NickNaso , you've been really helpful. I will close this issue and I'm planning to write an article on this as soon as possible. I'll share it when its done :)

馃憤 Yes I hope to read your article very soon.

Was this page helpful?
0 / 5 - 0 ratings