4

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

Community
  • 1
  • 1
Ray Burgemeestre
  • 1,230
  • 8
  • 13
  • Please update your question to concisely state your current question, as there are no responses to it yet. We don't need to know your process for getting to where you currently are. Or if you no longer need help, perhaps just delete the question altogether. – xaxxon Jan 04 '17 at 05:58
  • @xaxxon Yeah I still need help with this, I've removed the process part – Ray Burgemeestre Jan 11 '17 at 08:16
  • You don't seem to be doing much error checking. Does the if(value->IsFunction()) part return true? If that if never runs, that would explain your return value – xaxxon Jan 11 '17 at 20:55
  • Yeah good point, it returns `false`, and if(value->IsUndefined()) returns true. I'll try some more and update the question again. I'm now figuring out if I can use something like v8::FunctionTemplate/NewInstance(). Though all snippets I can find seem to construct something entirely new, and I want to fetch the "class" from an existing context. – Ray Burgemeestre Jan 11 '17 at 23:40
  • i glanced around to see what I could see about the object model for es6 classes and didn't see anything. – xaxxon Jan 12 '17 at 02:15
  • I'd put some code directly here using the same context variable used for making the script and do some simpler tests.. https://gist.github.com/rayburgemeestre/c0abd528f6f67edbfe686d484c45ddbb#file-testcase-cpp-L94 instead of calling out to the other function.. just mess around with the v8 object model directly to see what you see.. also some "dumper" classes can be really handy to see what's going on. Here's something I wrote that probably sucks: https://github.com/xaxxon/v8toolkit/blob/master/src/v8helpers.cpp#L160 – xaxxon Jan 12 '17 at 02:29
  • Thanks xaxxon, I've updated the code, it now uses this helper: https://gist.github.com/rayburgemeestre/df6193d532c7b7908fe27c89799bfa3a I'm considering posting my question to the V8 mailinglist, will update here if I get the answer – Ray Burgemeestre Jan 12 '17 at 13:29
  • the v8-users mailing list is quite helpful, especially if you post full standalone working code. I'm very interested in the answer. Just playing around in chrome console, it seems that es6class.constructor() returns a function, which I find interesting -- but I can't figure out what that function does – xaxxon Jan 12 '17 at 17:27
  • http://exploringjs.com/es6/ch_variables.html -- variables declared with 'class' are block scoped Just like if you say let foo = 5; global.foo is undefined – xaxxon Jan 12 '17 at 17:50

1 Answers1

2

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)

xaxxon
  • 19,189
  • 5
  • 50
  • 80
  • First of all thanks a bunch, I think I now understand why I couldn't refer to the class, and whenever I tried to create an instance in C++ I'd just get a new object with the same name :) The first approach, making a global variable for the class works for me and is more performant than the current workaround I was using until now (using a factory function) – Ray Burgemeestre Jan 15 '17 at 11:15
  • @RayBurgemeestre glad we got it figured out and I learned quite a bit in the process. – xaxxon Jan 16 '17 at 07:27