Calling javascript function from C++ Addon

I need some help to solve my problem. Basically, I want to "call" javascript function from C++ addon, I've googled about it and found something like this.

Here is my .js file

const myaddon = process.binding('myaddon');

function Foo() {
    console.log("wooo");
}
myaddon.getfoo(Foo);
myaddon.callfoo(); // just to test if C++ Call js function work

Here is my .cpp

Local<Function> pfOnScriptInit;
Local<Object> globel;
void test(const FunctionCallbackInfo<Value>& args)
{
    pfOnScriptInit = Local<Function>::Cast(args[0]);
}
void call(const FunctionCallbackInfo<Value>& args)
{
    pfOnScriptInit->Call(globel, 0, nullptr);
}
void initAll(Local<Object> target, Local<Value> unused, Local<Context> context, void* priv) 
{
    node::Environment* env = node::Environment::GetCurrent(context);

    globel = env->context()->Global();
    env->SetMethod(target, "getfoo", test); 
    env->SetMethod(target, "callfoo", call);
}
NODE_MODULE_CONTEXT_AWARE_BUILTIN(fivemp, node::fivemp::initAll)

I got this error when calling myaddon.callfoo(); function:

TypeError: Illegal Invocation

Upvotes: 3

Views: 1934

Answers (1)

Ezequiel Garcia
Ezequiel Garcia

Reputation: 1057

This question is kinda old, but it's a common question, which has been asked a couple times. These answers don't have a proper answer in my opinion. Hence, here I go!

Before I answer your question, let me say that you should use Native Abstractions for Node.js to write a Node.js addon. This will make your addon portable across Node.js versions.

Also, you should take a look at the official addon documentation here, and to the repo with Nan examples.

That said, your code has a big and common mistake: you are confusingly using a local object as persistent. In other words, declaring a Local<Function> out of a function scope is plain wrong. These two cannot be declared anywhere but in function scope:

Local<Function> pfOnScriptInit;
Local<Object> globel;

And of course, you cannot assign a Local<Function> like this:

pfOnScriptInit = Local<Function>::Cast(args[0]);

This local object is going to be reclaimed by v8's garbage collector - it won't exist anymore. So, instead you need to use a persistent type. Here is an example, written using Nan, which might show you how it's done:

#include <nan.h>

static Nan::CopyablePersistentTraits<v8::Function>::CopyablePersistent _cb;

static void SetCallback(const Nan::FunctionCallbackInfo<v8::Value>& info) {
  _cb = Nan::Persistent<v8::Function>(info[0].As<v8::Function>());
}

static void RunCallback(const Nan::FunctionCallbackInfo<v8::Value>& info) {
  Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(_cb), 0, 0);
  _cb.Reset();
}

void RunThisCallback(const Nan::FunctionCallbackInfo<v8::Value>& info) {
  v8::Local<v8::Function> cb = info[0].As<v8::Function>();
  Nan::MakeCallback(Nan::GetCurrentContext()->Global(), cb, 0, 0);
}

static void Init(v8::Local<v8::Object> exports, v8::Local<v8::Object> module) {
  Nan::SetMethod(exports, "setCallback", SetCallback);
  Nan::SetMethod(exports, "call", RunCallback);
  Nan::SetMethod(exports, "callThis", RunThisCallback);
}

NODE_MODULE(addon, Init)

And here's the JS side:

var addon = require('bindings')('addon');

function foo() {
        console.log("woohoo!");
}

addon.setCallback(foo);
addon.call();
addon.callThis(() => console.log("this too!"));

For more information about v8's local vs persistent, you should take a look at v8's documentation. There is a also a good book about Node.js addons by Scott Frees, here https://scottfrees.com/ebooks/nodecpp/.

Upvotes: 5

Related Questions