5

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!

simon-p-r
  • 3,623
  • 2
  • 20
  • 35
logidelic
  • 1,525
  • 15
  • 42

2 Answers2

8

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.

bmacnaughton
  • 4,950
  • 3
  • 27
  • 36
mkrufky
  • 3,268
  • 2
  • 17
  • 37
  • 1
    This is a great answer with a clear example, though adding an array would make it even better. It's not clear why the OP hasn't accepted the answer. I would if I could. – bmacnaughton Jan 28 '18 at 12:23
3

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.

Shawon Kanji
  • 710
  • 5
  • 13
  • 1
    Thank you for the thorough response. However, while I'm not sure what the stackoverflow rules are, I think you should be giving credit to Scott Frees as the snippets appear to be taken from his blog... – logidelic Sep 19 '17 at 13:19