简体   繁体   中英

Passing on a C library callback to a NodeJS EventEmitter using node-addon-api

I am currently working on writing a node module that binds Alex Diner's cross-platform gamepad code using the Node-Addon-API .

For the most part, this was a fairly easy task. Only problematic are the callback functions of the Gamepad library.

My idea is to expose these callbacks via .on(...) by making the module an EventEmitter. It was already done in a similar way with the Nan module in this node module .

The problem is, whenever an event is fired from my native module, I get the following error:

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)

The important parts of my 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 stackoverflow 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;
}

And the index.js that connects the C++ module with the EventEmitter:

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

If more information is needed, dont hesitate to ask!

Thanks in advance: :)

Okay, so I was able to solve this by myself. I am not sure why this is a problem, but the error happens due to the fact that I am calling the.emit(event, ...args) method from the EventEmitter class directly from C++.

I simply added a wrapper function to my module from JavaScript, and now it works like a charm:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM