关于Node和C++互相调用的问题

最近看了很多的相关文档;但是有有一个地方一直很不解,关于Node调用C++传递回调函数:
官方文档如下:

C++部分:

// addon.cc
#include <node.h>

namespace demo {

using v8::Function;
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Null;
using v8::Object;
using v8::String;
using v8::Value;

void RunCallback(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  Local<Function> cb = Local<Function>::Cast(args[0]);
  const unsigned argc = 1;
  Local<Value> argv[argc] = { String::NewFromUtf8(isolate, "hello world") };
  cb->Call(Null(isolate), argc, argv);
}

void Init(Local<Object> exports, Local<Object> module) {
  NODE_SET_METHOD(module, "exports", RunCallback);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, Init)

}  // namespace demo

js部分

// test.js
const addon = require('./build/Release/addon');

addon((msg) => {
  console.log(msg);
// Prints: 'hello world'
});

可以看得出来,这个demo中js虽然传递的是function,但是在C++模块是同步的执行的;而且同步执行的话我也调通了,但是实际的场景肯定不是这种需求,而是在C++模块某一个异步的事件监听中收到某一种消息之后才通过js模块传递的回调函数回调给js;

目前已经做好的事情:
封装一个C++的类,并且export,可用js调用此类的实例方法;

.h文件的两个属性:

Isolate *onPlayNoteIsolate;
Local <Function> onPlayNote;

.cpp文件

// 标记为方法1:
void MIDIDeviceHelperBridge::setOnPlayNote(const FunctionCallbackInfo<Value>& args) {
    MIDIDeviceHelperBridge *obj = ObjectWrap::Unwrap<MIDIDeviceHelperBridge>(args.Holder());
    
    Local<Function> callback = Local<Function>::Cast(args[1]);
    obj->onPlayNote = callback;
    obj->onPlayNoteIsolate = args.GetIsolate();
}

// 标记为方法2:
void MIDIDeviceHelperBridge::ReceiveMsg(DWORD Msg, DWORD TimeStamp) {
    // 此方法中的this为方法1中的obj
}

我现在的需求是在方法1中获得的js模块的回调函数存储在obj中或者任何在C++模块能访问到的地方,然后在方法2中收到消息之后通过方法1获得的js模块的回调,将消息传递给js模块;

我尝试过在方法1中的obj里存储callback和isolate,然后在方法2中通过this访问callback和isolate,但是收到消息后程序崩溃了,发现isolate没有问题,是callback被释放了,后来也尝试过Local <Function> onPlayNote;
类型换成Persistent <Function> onPlayNote;和Handle<Function> onPlayNote;也都是收到消息后崩溃;

现求问有没有大神做过类似的功能?

========================================================================
经过几个小时后修改部分问题:

1:Persistent <Function> onPlayNote;不存储在类的成员变量中,放到全局,而且必须使用Persistent;
2:发现的其他问题,ReceiveMsg方法我可以在当前类对js公开的方法中调用,并且在js中调用词方法后,js模块的回调也是可以执行的;
3:但实际中ReceiveMsg不是我手动调用的,而是MIDI设备(例如能通过USB连接电脑的电子琴)上的按键触发后通过windows系统的midiapi调用ReceiveMsg方法,此时ReceiveMsg中没有任何js的环境;

========================================================================

阅读 4.5k
3 个回答

已解决
收到的消息存储在当前对象的消息队列,然后通过emit发送到js模块:

C++部分

// 在类的New方法中,用类的静态方法实例化runloop中的异步函数
void MIDIDeviceHelperBridge::New(const FunctionCallbackInfo<Value>& args) {
    MIDIDeviceHelperBridge *obj = new MIDIDeviceHelperBridge();
    obj->isolate = isolate;
    obj->message_async.data = obj;
    uv_async_init(uv_default_loop(), &obj->message_async, MIDIDeviceHelperBridge::MIDIMessageCallback);
    ……
}

void MIDIDeviceHelperBridge::ReceiveMsg(DWORD Msg, DWORD TimeStamp) {
    uv_async_send(&this->message_async);
}

void MIDIDeviceHelperBridge::MIDIMessageCallback(uv_async_t *async) {
    MIDIDeviceHelperBridge *obj = static_cast<MIDIDeviceHelperBridge*>(async->data);
    Local<v8::Function> emitFunction = obj->handle()->Get(String::NewFromUtf8(obj->isolate, "emit")).As<v8::Function>();
    const unsigned argc = 4;
    char *CustomMessageName = "CustomMessageName";
    Local <Value> argv[argc] = {
        String::NewFromUtf8(obj->isolate, CustomMessageName),
        Number::New(obj->isolate, 1),
        Number::New(obj->isolate, 2),
        Number::New(obj->isolate, 3)
    };
    MakeCallback(obj->isolate, obj->handle(), emitFunction, argc, argv);
}

js部分:

const midiDeviceHelper = require(`xxx.node`);//路径自己写上就行
const EventEmitter = require('events').EventEmitter;
midiDeviceHelper.MIDIDeviceHelperBridge.prototype.__proto__ = EventEmitter.prototype;
const midiDeviceHelperBridge = new midiDeviceHelper.MIDIDeviceHelperBridge();
const CustomMessageName = "CustomMessageName"; //这个是上面argv里的第一个元素
midiDeviceHelperBridge.on(CustomMessageName, (a, b, c) => {
});

请问MIDIDeviceHelperBridge类的handle()方法怎么实现的?
跪求完整的例子?@kingphone_he

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题