How to return a new V8 javascript "class" instance from within C++?

1.4k views Asked by At

When using V8 as a scripting engine I expose a C++ function called construct_with_ec6_syntax to Javascript. This function, when invoked, should simply return an instance of some_ec6_class.

This C++ function should basically do the following equivalent Javascript:

return new some_ec6_class(111, 222);

This class will be defined in Javascript as follows with EC6 syntax:

class some_ec6_class
{
    constructor(x, y) {
        this.result = x + y;
    }
}

My goal is to run the following in Javascript...

var the_instance = construct_with_ec6_syntax(111, 222);
the_instance.result ; // .. and get 333 here.

My current implementation for the C++ function is this:

void construct_with_ec6_syntax(const FunctionCallbackInfo<Value>& args) {
    Handle<Object> global = args.GetIsolate()->GetCurrentContext()->Global();
    std::string newvecfunc = "some_ec6_class";
    Handle<Value> value = global->Get(String::NewFromUtf8(args.GetIsolate(), newvecfunc.c_str(), String::kNormalString, newvecfunc.length()));
    Local<Value> result;
    if (value->IsFunction()) {
        Handle<Function> func = Handle<Function>::Cast(value);
        Handle<Value> args2[2];
        args2[0] = Number::New(args.GetIsolate(), 111);
        args2[1] = Number::New(args.GetIsolate(), 222);
        result = func->CallAsConstructor(args.GetIsolate()->GetCurrentContext(), 2, args2).ToLocalChecked();
    }   
    args.GetReturnValue().Set(result);
}   

Running this function from Javascript has it return undefined! Instead of the object I expect. As xaxxon pointed out to me, it's because value->IsFunction() returns false, but value->IsUndefined() returns true. If I had defined the class using the with non EC6 syntax, the above function does return an instance..

function some_non_ec6_class(x, y) // this guy would work with the above function
{
    this.result = x + y;
}

So I'm a bit confused! Do I somehow need to be more specific, like get the constructor function from the object first, and then invoke CallAsConstructor?

Any hint is appreciated!

(This question looks similar to Calling a v8 javascript function from c++ with an argument but is different.)

I'm using a V8 checkout from 22nd of October 2016. Complete testcase:

https://gist.github.com/rayburgemeestre/c0abd528f6f67edbfe686d484c45ddbb

Minor update:

As you can see in the comments, I also made a more specific test-case regarding "fetching" the class from the context here: https://gist.github.com/rayburgemeestre/df6193d532c7b7908fe27c89799bfa3a

Also I posted to the v8-users mailinglist: https://groups.google.com/forum/#!topic/v8-users/Hj2j4rJMwBw

1

There are 1 answers

2
xaxxon On BEST ANSWER

class is a way of creating a variable in javascript, similar to let. Variables created with class are block scoped and do not create global properties (again, similar to let). Therefor it is not available in context.global like a function is:

http://exploringjs.com/es6/ch_variables.html

function    Complete    Block   Yes

class       No          Block   No

You would need to explicitly create a global variable for your code to work:

some_es6_class = class{}; 

or explicitly pass in the class object to your c++ function after creating it with the 'traditional es6' syntax:

class some_es6_class{};
some_native_function(some_es6_class);

Edit: I've done some more digging and I believe that the context object has a LexicalEnvironment that is shared across its scripts but is distinct from the global object. When a name is looked up, it traverses a parent hierarchy of LexicalEnvironments looking for the name. These lexical environments are not exposed via the API (and may not actually exist - they are a construct used by the JS specification to define behavior, not a required part of an implementation)