使用 node-addon-api 将 C 库回调传递给 NodeJS EventEmitter
Passing on a C library callback to a NodeJS EventEmitter using node-addon-api
我目前正在编写一个绑定 Alex Diner 的节点模块 cross-platform gamepad code using the Node-Addon-API。
在大多数情况下,这是一项相当容易的任务。
唯一有问题的是 Gamepad 库的回调函数。
我的想法是通过 .on(...)
通过使模块成为 EventEmitter 来公开这些回调。 Nan module in this node module.
已经以类似的方式完成了
问题是,每当从我的本机模块触发事件时,我都会收到以下错误:
internal/timers.js:531
timer._onTimeout();
^
TypeError: Cannot read property '_events' of undefined
at emit (events.js:163:23)
at listOnTimeout (internal/timers.js:531:17)
at processTimers (internal/timers.js:475:7)
我binding.cc
的重要部分:
#include <napi.h>
#include "gamepad/Gamepad.h" // The Gamepad library written by Alex Diner
// The JS .emit(event, ...args) function from the EventEmitter
Napi::FunctionReference emitFn;
void GamepadSetEmitFn(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (info.Length() < 1) {
Napi::TypeError::New(env, "Parameter 'emitFn' needs to be defined.")
.ThrowAsJavaScriptException();
return;
}
if (!info[0].IsFunction()) {
Napi::TypeError::New(env, "Parameter 'emitFn' needs to be a function.")
.ThrowAsJavaScriptException();
return;
}
// Sets the emitFn to the specified function
emitFn = Napi::Persistent(info[0].As<Napi::Function>());
}
// The following handle functions are the callbacks for the actual Gamepad module
void HandleDeviceAttach(struct Gamepad_device* device, void* context) {
if (emitFn == nullptr) return;
Napi::Env env = emitFn.Env();
Napi::String eventName = Napi::String::New(env, "attach");
Napi::Number nDeviceID = Napi::Number::New(env, device->deviceID);
emitFn.Call({ eventName, nDeviceID });
}
void HandleDeviceRemove(struct Gamepad_device* device, void* context) {
if (emitFn == nullptr) return;
Napi::Env env = emitFn.Env();
Napi::String eventName = Napi::String::New(env, "remove");
Napi::Number nDeviceID = Napi::Number::New(env, device->deviceID);
emitFn.Call({ eventName, nDeviceID });
}
void HandleButtonDown(struct Gamepad_device* device, unsigned int buttonID, double timestamp, void* context) {
if (emitFn == nullptr) return;
Napi::Env env = emitFn.Env();
Napi::String eventName = Napi::String::New(env, "down");
Napi::Number nDeviceID = Napi::Number::New(env, device->deviceID);
Napi::Number nButtonID = Napi::Number::New(env, buttonID);
Napi::Number nTimestamp = Napi::Number::New(env, timestamp);
emitFn.Call({ eventName, nDeviceID, nButtonID, nTimestamp });
}
void HandleButtonUp(struct Gamepad_device* device, unsigned int buttonID, double timestamp, void* context) {
if (emitFn == nullptr) return;
Napi::Env env = emitFn.Env();
Napi::String eventName = Napi::String::New(env, "up");
Napi::Number nDeviceID = Napi::Number::New(env, device->deviceID);
Napi::Number nButtonID = Napi::Number::New(env, buttonID);
Napi::Number nTimestamp = Napi::Number::New(env, timestamp);
emitFn.Call({ eventName, nDeviceID, nButtonID, nTimestamp });
}
void HandleAxisMovement(struct Gamepad_device* device, unsigned int axisID, float value, float lastValue, double timestamp, void* context) {
if (emitFn == nullptr) return;
Napi::Env env = emitFn.Env();
Napi::String eventName = Napi::String::New(env, "move");
Napi::Number nDeviceID = Napi::Number::New(env, device->deviceID);
Napi::Number nAxisID = Napi::Number::New(env, axisID);
Napi::Number nValue = Napi::Number::New(env, value);
Napi::Number nLastValue = Napi::Number::New(env, lastValue);
Napi::Number nTimestamp = Napi::Number::New(env, timestamp);
emitFn.Call({ eventName, nDeviceID, nAxisID, nValue, nLastValue, nTimestamp });
}
Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
// Applies the specified callback functions above to the respective Gamepad function.
Gamepad_deviceAttachFunc(HandleDeviceAttach, NULL);
Gamepad_deviceRemoveFunc(HandleDeviceRemove, NULL);
Gamepad_buttonDownFunc(HandleButtonDown, NULL);
Gamepad_buttonUpFunc(HandleButtonUp, NULL);
Gamepad_axisMoveFunc(HandleAxisMovement, NULL);
// All functionality exposed to JS
// (including the function implementations omitted from this Whosebug excerpt)
exports.Set(Napi::String::New(env, "init"), Napi::Function::New(env, GamepadInit));
exports.Set(Napi::String::New(env, "shutdown"), Napi::Function::New(env, GamepadShutdown));
exports.Set(Napi::String::New(env, "detectDevices"), Napi::Function::New(env, GamepadDetectDevices));
exports.Set(Napi::String::New(env, "processEvents"), Napi::Function::New(env, GamepadProcessEvents));
exports.Set(Napi::String::New(env, "numDevices"), Napi::Function::New(env, GamepadNumDevices));
exports.Set(Napi::String::New(env, "deviceAtIndex"), Napi::Function::New(env, GamepadDeviceAtIndex));
exports.Set(Napi::String::New(env, "setEmitFn"), Napi::Function::New(env, GamepadSetEmitFn));
return exports;
}
以及连接 C++ 模块和 EventEmitter 的 index.js
:
const gamepad = require('../build/Release/gamepad.node')
const { EventEmitter } = require('events')
// Makes the native module extend 'EventEmitter'
gamepad.__proto__ = EventEmitter.prototype
// Exposes the emit function to the native module
gamepad.setEmitFn(gamepad.emit)
module.exports = gamepad
如果需要更多信息,请随时询问!
提前致谢! :)
好的,所以我能够自己解决这个问题。我不确定为什么这是一个问题,但由于我直接从 C++ class 从 EventEmitter class 调用 .emit(event, ...args) 方法而发生错误。
我只是从 JavaScript 向我的模块添加了一个包装函数,现在它运行起来非常棒:
const gamepad = require('../build/Release/gamepad.node')
const { EventEmitter } = require('events')
// Makes the native module extend 'EventEmitter'
gamepad.__proto__ = EventEmitter.prototype
// Wraps the emit function for the call from the native module
gamepad.applyEmit = (...args) => {
const event = args.shift()
gamepad.emit(event, ...args)
}
// Exposes the applyEmit function to the native module
gamepad.setEmitFn(gamepad.applyEmit)
module.exports = gamepad
我目前正在编写一个绑定 Alex Diner 的节点模块 cross-platform gamepad code using the Node-Addon-API。
在大多数情况下,这是一项相当容易的任务。 唯一有问题的是 Gamepad 库的回调函数。
我的想法是通过 .on(...)
通过使模块成为 EventEmitter 来公开这些回调。 Nan module in this node module.
问题是,每当从我的本机模块触发事件时,我都会收到以下错误:
internal/timers.js:531
timer._onTimeout();
^
TypeError: Cannot read property '_events' of undefined
at emit (events.js:163:23)
at listOnTimeout (internal/timers.js:531:17)
at processTimers (internal/timers.js:475:7)
我binding.cc
的重要部分:
#include <napi.h>
#include "gamepad/Gamepad.h" // The Gamepad library written by Alex Diner
// The JS .emit(event, ...args) function from the EventEmitter
Napi::FunctionReference emitFn;
void GamepadSetEmitFn(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (info.Length() < 1) {
Napi::TypeError::New(env, "Parameter 'emitFn' needs to be defined.")
.ThrowAsJavaScriptException();
return;
}
if (!info[0].IsFunction()) {
Napi::TypeError::New(env, "Parameter 'emitFn' needs to be a function.")
.ThrowAsJavaScriptException();
return;
}
// Sets the emitFn to the specified function
emitFn = Napi::Persistent(info[0].As<Napi::Function>());
}
// The following handle functions are the callbacks for the actual Gamepad module
void HandleDeviceAttach(struct Gamepad_device* device, void* context) {
if (emitFn == nullptr) return;
Napi::Env env = emitFn.Env();
Napi::String eventName = Napi::String::New(env, "attach");
Napi::Number nDeviceID = Napi::Number::New(env, device->deviceID);
emitFn.Call({ eventName, nDeviceID });
}
void HandleDeviceRemove(struct Gamepad_device* device, void* context) {
if (emitFn == nullptr) return;
Napi::Env env = emitFn.Env();
Napi::String eventName = Napi::String::New(env, "remove");
Napi::Number nDeviceID = Napi::Number::New(env, device->deviceID);
emitFn.Call({ eventName, nDeviceID });
}
void HandleButtonDown(struct Gamepad_device* device, unsigned int buttonID, double timestamp, void* context) {
if (emitFn == nullptr) return;
Napi::Env env = emitFn.Env();
Napi::String eventName = Napi::String::New(env, "down");
Napi::Number nDeviceID = Napi::Number::New(env, device->deviceID);
Napi::Number nButtonID = Napi::Number::New(env, buttonID);
Napi::Number nTimestamp = Napi::Number::New(env, timestamp);
emitFn.Call({ eventName, nDeviceID, nButtonID, nTimestamp });
}
void HandleButtonUp(struct Gamepad_device* device, unsigned int buttonID, double timestamp, void* context) {
if (emitFn == nullptr) return;
Napi::Env env = emitFn.Env();
Napi::String eventName = Napi::String::New(env, "up");
Napi::Number nDeviceID = Napi::Number::New(env, device->deviceID);
Napi::Number nButtonID = Napi::Number::New(env, buttonID);
Napi::Number nTimestamp = Napi::Number::New(env, timestamp);
emitFn.Call({ eventName, nDeviceID, nButtonID, nTimestamp });
}
void HandleAxisMovement(struct Gamepad_device* device, unsigned int axisID, float value, float lastValue, double timestamp, void* context) {
if (emitFn == nullptr) return;
Napi::Env env = emitFn.Env();
Napi::String eventName = Napi::String::New(env, "move");
Napi::Number nDeviceID = Napi::Number::New(env, device->deviceID);
Napi::Number nAxisID = Napi::Number::New(env, axisID);
Napi::Number nValue = Napi::Number::New(env, value);
Napi::Number nLastValue = Napi::Number::New(env, lastValue);
Napi::Number nTimestamp = Napi::Number::New(env, timestamp);
emitFn.Call({ eventName, nDeviceID, nAxisID, nValue, nLastValue, nTimestamp });
}
Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
// Applies the specified callback functions above to the respective Gamepad function.
Gamepad_deviceAttachFunc(HandleDeviceAttach, NULL);
Gamepad_deviceRemoveFunc(HandleDeviceRemove, NULL);
Gamepad_buttonDownFunc(HandleButtonDown, NULL);
Gamepad_buttonUpFunc(HandleButtonUp, NULL);
Gamepad_axisMoveFunc(HandleAxisMovement, NULL);
// All functionality exposed to JS
// (including the function implementations omitted from this Whosebug excerpt)
exports.Set(Napi::String::New(env, "init"), Napi::Function::New(env, GamepadInit));
exports.Set(Napi::String::New(env, "shutdown"), Napi::Function::New(env, GamepadShutdown));
exports.Set(Napi::String::New(env, "detectDevices"), Napi::Function::New(env, GamepadDetectDevices));
exports.Set(Napi::String::New(env, "processEvents"), Napi::Function::New(env, GamepadProcessEvents));
exports.Set(Napi::String::New(env, "numDevices"), Napi::Function::New(env, GamepadNumDevices));
exports.Set(Napi::String::New(env, "deviceAtIndex"), Napi::Function::New(env, GamepadDeviceAtIndex));
exports.Set(Napi::String::New(env, "setEmitFn"), Napi::Function::New(env, GamepadSetEmitFn));
return exports;
}
以及连接 C++ 模块和 EventEmitter 的 index.js
:
const gamepad = require('../build/Release/gamepad.node')
const { EventEmitter } = require('events')
// Makes the native module extend 'EventEmitter'
gamepad.__proto__ = EventEmitter.prototype
// Exposes the emit function to the native module
gamepad.setEmitFn(gamepad.emit)
module.exports = gamepad
如果需要更多信息,请随时询问!
提前致谢! :)
好的,所以我能够自己解决这个问题。我不确定为什么这是一个问题,但由于我直接从 C++ class 从 EventEmitter class 调用 .emit(event, ...args) 方法而发生错误。
我只是从 JavaScript 向我的模块添加了一个包装函数,现在它运行起来非常棒:
const gamepad = require('../build/Release/gamepad.node')
const { EventEmitter } = require('events')
// Makes the native module extend 'EventEmitter'
gamepad.__proto__ = EventEmitter.prototype
// Wraps the emit function for the call from the native module
gamepad.applyEmit = (...args) => {
const event = args.shift()
gamepad.emit(event, ...args)
}
// Exposes the applyEmit function to the native module
gamepad.setEmitFn(gamepad.applyEmit)
module.exports = gamepad