c++node.jsnode.js-addonnode-addon-apin-api

Streaming data into a Node.js C++ Addon with N-API


I am building a C++ addon for NodeJS and I want to stream data asynchronously from C++ to Node. I have found this article, https://nodeaddons.com/streaming-data-into-a-node-js-c-addon/, however; I want to use the N-API instead of NAN.

I have been searching through the NodeJS docs and examples as well as looking for other resources and examples but have not come across a resource to show me how I can accomplish this. This is my first time writing a C++ addon for NodeJS.

An example that would help me get started would be an addon that uses the N-API to send a dummy string every second to Node and Node would print the string to the console.


Solution

  • Here is a snippet based on EventEmitter concept, which simulates reading of sensors from native layer (C/C++) and push the data to Node.js (JavaScript) layer. In this example we are using node-addon-api, a header-only C++ wrapper to N-API. Here for this example we have used a for loop (with only five iteratins), in reality it could be an infinite loop continuously reading sensors outputs and pushing the data to JS layer. The native layer can decide when to report the data it got collected from sensors to the JS layer. The JS will receive the data asynchronous for the subscribed events.

    #include <napi.h>
    #include <thread>
    
    Napi::Value CallEmit(const Napi::CallbackInfo &info)
    {
        char buff1[128];
        char buff2[128];
        int  sensor1 = 0;
        int  sensor2 = 0;
    
        Napi::Env env = info.Env();
    
        Napi::Function emit = info[0].As<Napi::Function>();
        emit.Call(  {Napi::String::New(env, "start")}  );
    
        for (int i = 0; i < 5; i++)
        {
            // Let us simulate some delay for collecting data from its sensors
            std::this_thread::sleep_for(std::chrono::seconds(2));
    
            sprintf(buff1, "sensor1 data %d ...", ++sensor1);
    
            emit.Call( { Napi::String::New(env, "sensor1"),
                       Napi::String::New(env, buff1 ) } );
    
            // Let, sensor 2 data is reported half the rate as sensor1
            if (i % 2)
            {
                sprintf(buff2, "sensor2 data %d ...", ++sensor2);
                emit.Call({ Napi::String::New(env, "sensor2"),
                           Napi::String::New(env, buff2) });
            }
        }
    
        emit.Call( {Napi::String::New(env, "end")} );
        return Napi::String::New( env, "OK" );
    }
    

    The module registration snippet is

    #include <napi.h>
    
    Napi::Object Init( Napi::Env env, Napi::Object exports )
    {
      exports.Set(Napi::String::New(env, "callEmit"), Napi::Function::New(env, CallEmit));
      return exports;
    }
    
    NODE_API_MODULE( myncpp1, Init )
    

    Compile the above native code and once it is successfully built then you can run the following node.js JavaScript code to test it.

    'use strict'
    
    const EventEmitter = require('events').EventEmitter
    const addon = require('bindings')('myncpp1')
    
    // General theme of EventEmitter is: notify me when it is ready
    
    function Main() {
        const emitter = new EventEmitter()
    
        emitter.on('start', () => {
            console.log( '### Sensor reading started ...');
        })
    
        emitter.on('sensor1', (evt) => {
            // This module will be called as on when the
            // sensor1 data available for consuming from JS
            console.log(evt);
        })
    
        emitter.on('sensor2', (evt) => {
            console.log(evt);
        })
    
        emitter.on('end', () => {
            console.log('### Sensor reading Ended');
        })
    
        addon.callEmit( emitter.emit.bind(emitter) )
    }
    
    Main();
    

    The code snippet should produce the following output.

    ### Sensor reading started ...
    sensor1 data 1 ...
    sensor1 data 2 ...
    sensor2 data 1 ...
    sensor1 data 3 ...
    sensor1 data 4 ...
    sensor2 data 2 ...
    sensor1 data 5 ...
    ### Sensor reading Ended