Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass arbitrary Javascript data object to Node.js C++ addon

I have a Node.js addon written in C++ using Nan. Works fantastically. However, I've not been able to figure out how to have my Node Javascript code pass an arbitrary data object (ex. {attr1:42, attr2:'hi', attr3:[5,4,3,2,1]}) to the C++ addon.

Until now, I've got around this by calling JSON.stringify() on my data object and then parsing the stringified JSON on the C++ side.

Ideally, I'd like to avoid copying data and just get a reference to the data object that I can access, or at least to copy it natively and avoid stringifying/parsing...

Any help would be appreciated!

like image 476
logidelic Avatar asked Sep 14 '17 15:09

logidelic


2 Answers

You can allow your Node.js c++ addons to take arbitrary typed arguments, but you must check and handle the types explicitly. He is a simple example function that shows how to do this:

void args(const Nan::FunctionCallbackInfo<v8::Value>& info) {

    int i = 0;
    while (i < info.Length()) {
        if (info[i]->IsBoolean()) {
            printf("boolean = %s", info[i]->BooleanValue() ? "true" : "false");
        } else if (info[i]->IsInt32()) {
            printf("int32 = %ld", info[i]->IntegerValue());
        } else if (info[i]->IsNumber()) {
            printf("number = %f", info[i]->NumberValue());
        } else if (info[i]->IsString()) {
            printf("string = %s", *v8::String::Utf8Value(info[i]->ToString()));
        } else if (info[i]->IsObject()) {
            printf("[object]");
            v8::Local<v8::Object> obj = info[i]->ToObject();
            v8::Local<v8::Array> props = obj->GetPropertyNames();
            for (unsigned int j = 0; j < props->Length(); j++) {
                printf("%s: %s",
                       *v8::String::Utf8Value(props->Get(j)->ToString()),
                       *v8::String::Utf8Value(obj->Get(props->Get(j))->ToString())
                      );
            }
        } else if (info[i]->IsUndefined()) {
            printf("[undefined]");
        } else if (info[i]->IsNull()) {
            printf("[null]");
        }
        i += 1;
    }
}

To actually solve the problem of handling arbitrary arguments that may contain objects with arbitrary data, I would recommend writing a function that parses an actual object similar to how I parsed function arguments in this example. Keep in mind that you may need to do this recursively if you want to be able to handle nested objects within the object.

like image 132
mkrufky Avatar answered Nov 14 '22 23:11

mkrufky


You don't have to stringify your object to pass it to c++ addons. There are methods to accept those arbitary objects. But it is not so arbitary. You have to write different codes to parse the object in c++ . Think of it as a schema of a database. You can not save different format data in a single collection/table. You will need another table/collection with the specific schema.

Let's see this example:

We will pass an object {x: 10 , y: 5} to addon, and c++ addon will return another object with sum and product of the properties like this: {x1:15,y1: 50}

In cpp code :

NAN_METHOD(func1) {
        if (info.Length() > 0) {
                Local<Object> obj = info[0]->ToObject();
                Local<String> x = Nan::New<String>("x").ToLocalChecked();
                Local<String> y = Nan::New<String>("y").ToLocalChecked();

                Local<String> sum  = Nan::New<String>("sum").ToLocalChecked();
                Local<String> prod  = Nan::New<String>("prod").ToLocalChecked();

                Local<Object> ret = Nan::New<Object>();

                double x1 = Nan::Get(obj, x).ToLocalChecked()->NumberValue();
                double y1 = Nan::Get(obj, y).ToLocalChecked()->NumberValue();

                Nan::Set(ret, sum, Nan::New<Number>(x1 + y1));
                Nan::Set(ret, prod, Nan::New<Number>(x1 * y1));

                info.GetReturnValue().Set(ret);

        }
}

In javascript::

const addon = require('./build/Release/addon.node');
var obj = addon.func1({ 'x': 5, 'y': 10 });
console.log(obj); // { sum: 15, prod: 50 }

Here you can only send {x: (Number), y: (number)} type object to addon only. Else it will not be able to parse or retrieve data.

Like this for the array:

In cpp:

NAN_METHOD(func2) {
    Local<Array> array = Local<Array>::Cast(info[0]);

    Local<String> ss_prop = Nan::New<String>("sum_of_squares").ToLocalChecked();
    Local<Array> squares = New<v8::Array>(array->Length());
    double ss = 0;

    for (unsigned int i = 0; i < array->Length(); i++ ) {
      if (Nan::Has(array, i).FromJust()) {
        // get data from a particular index
        double value = Nan::Get(array, i).ToLocalChecked()->NumberValue();

        // set a particular index - note the array parameter
        // is mutable
        Nan::Set(array, i, Nan::New<Number>(value + 1));
        Nan::Set(squares, i, Nan::New<Number>(value * value));
        ss += value*value;
      }
    }
    // set a non index property on the returned array.
    Nan::Set(squares, ss_prop, Nan::New<Number>(ss));
    info.GetReturnValue().Set(squares);
}

In javascript:

const addon = require('./build/Release/addon.node');
var arr = [1, 2, 3];
console.log(addon.func2(arr));  //[ 1, 4, 9, sum_of_squares: 14 ]

Like this, you can handle data types. If you want complex objects or operations, you just have to mix these methods in one function and parse the data.

like image 38
Shawon Kanji Avatar answered Nov 14 '22 22:11

Shawon Kanji