So, a problem arose.
I wanted to account for two possibilities in the Script Engine: Passing existing objects from C++ to JavaScript, and creating new C++ objects from JavaScript. The act of passing a native object into scripting languages is usually referred to as wrapping (although, at one point inside the engine, I actually refer to a part of the process as unwrapping).
The code I would like to be able to use is:
//create a point Point *point = new Point(.42, 42); //pass it to some JavaScript function myJavaScriptFunction->Call(pointWrap(point), 0, NULL);
//create a Point var point = new Point(); //set its X value point.x = 4.2; //call some C++ function using it myCPPFunction(point);
So how did I manage this in the engine? Well, as I am prone do, I overcomplicated it.
I started by finding examples. V8 being as young as it is, there weren’t many. However, I did manage to find one in some older posts on the v8-users discussion group. Modeling my code off that, I came, unfortunately, to the wrong conclusion. Instead of covering the wrong conclusion first, which I feel will merely lead to confusion, I’ll cover the way I discovered was correct.
First, a summary of class and prototype paradigms.
Paradigms
If you think about a class, you’ll realize there are a few different elements:
- Constructors
- Properties
- Functions
- (Maybe) Destructors
To wrap an object in a V8-accessible form (a v8::Object) you will want to connect some or all of these to JavaScript.
In JavaScript, if an object has a constructor, it is in fact an instance of that constructor. Consider:
//make a constructor var MyClass = function(argument) { this.value = argument; } //add functions all instances should have MyClass.prototype.say = function() { alert(this.value); } //make an instance var testClass = new MyClass("test"); testClass.say(); //alerts "test"
This creates a new instance of the MyClass function. Creating an instance calls the function, setting this.value to argument, which, in this case, equals “test.”
Google does not give you C++ APIs that work in the JavaScript paradigm. Nor do they give APIs that work in the C++ paradigm. I think they were trying to make it so that you could pretend the model was a class model (like C++), but there is only so far you can go while still maintaining the full ability to manipulate the JavaScript model. This adds a bit of confusion.
Wrapping
So, how do you force the rigid C++ class structures into this JavaScript prototype-based paradigm? There are two parts of this problem. The first is how to create new objects (constructors). The next is how those objects should act once they are responding.
We shall start with the second, because things are easier if you do them in that order.
Objects themselves (not their constructors) must be wrapped in V8 templates. I use FunctionTemplates to wrap the objects. The process is simple, and an example is on V8′s website. I’ve modified it a bit. They use ObjectTemplates. I use FunctionTemplates because they are more proper: not only does JavaScript use them to initialize objects, but they (not ObjectTemplates) are the only ones able to inherit from other templates (though I do not actually use that feature).
So, to prepare a template for, say, a Point, you might do something like this:
//create your function template Handle<FunctionTemplate> point_template = FunctionTemplate::New(); //get the point's instance template Handle<ObjectTemplate> point_instance_template = point_template->InstanceTemplate(); //set its internal field count to one (we'll put references to the C++ point here later) point_instance_template->SetInternalFieldCount(1); //add some properties (x and y) point_instance_template->SetAccessor(String::New("x"), GetPointX, SetPointX); point_instance_template->SetAccessor(String::New("y"), GetPointY, SetPointY);
Tada! Now you have a template.
Now that you have a template, you will want to wrap it:
Handle<Object> wrapPoint(Point *pointToWrap) { //enter a handle scope HandleScope handle_scope; //create a new point instance Local<Object> point_instance = point_templ->NewInstance(); //set that internal field point_instance->SetInternalField(0, External::New(pointToWrap)); //I'm skipping some Persistent MakeWeak goodness right here //to prevent the point_instance from being destroyed when its //scope handle_scope is, use the Close() function return handle_scope.Close(point_instance); }
That’s pretty simple. Now, what about constructors?
There are two ways to do this. The first is the way I ended up using.
This way is very simple. In it, you do not use the same FunctionTemplate for the constructor as you do for the template. That’s what I did at first, and I’ll explain why it didn’t work in a moment. While it may be a function, that does not mean it automatically works as a constructor.
Instead of reusing the same FunctionTemplate and simply giving it a C++ function to call, you can make a brand new FunctionTemplate:
//get your scope v8::HandleScope handle_scope; //Get your V8 Context Handle<v8::Context> context = myContext; //Start a context scope v8::Context::Scope context_scope(context); //Get the context's global scope (that's where we'll put the constructor) v8::Handle<v8::Object> global = context->Global(); //create function template for our constructor //it will call the constructPoint function v8::Handle<v8::FunctionTemplate> function = v8::FunctionTemplate::New(constructPoint); //set the function in the global scope -- that is, set "Point" to the constructor global->Set(v8::String::New("Point"), function->GetFunction());
And, of course, the function it calls:
v8::Handle<v8::Value>constructorCall(const Arguments &args) { // throw if called without `new' if (!args.IsConstructCall()) return ThrowException(String::New("Cannot call constructor as function")); //start a handle scope v8::HandleScope handle_scope; //get an x and y double x = args[0]->NumberValue(); double y = args[1]->NumberValue(); //generate a new point Point *point = new Point(x, y); //return the wrapped point return wrapPoint(point); }
Pretty simple. We simply create a point, and pass it to the point wrapper. Ironically, something akin to this is what I attempted first, although I did it wrong. I then looked for examples, and found one that did it incorrectly. Finally, I spent several hours debugging, until I finally figured out this seemingly proper, and very simple way (anyone, feel free to correct me). While my actual code is not quite as simple as this (as I do several memory management- and abstraction-related things in there as well), this covers the point.
So what did I do wrong?
What I Did Wrong
The mistake I made was to try to use only one FunctionTemplate. Instead of having two separate ones, I made one single one. It was both the constructor and the template. Both functions had their own wrapping code (I had tried calling the wrapping function from the constructor, but it caused a recursion error, for reasons that will become clear).
Constructing worked great, I couldn’t have been happier. But wrapping had problems. I noticed that even when I tried to wrap an existing object, the constructor function was called, creating a brand new one! After a bit of debugging, I traced it down to the function’s NewInstance function.
Remember that JavaScript, when it creates a new instance of a function, calls the function. Well, I was creating a new instance of the function, and so, since I had a function (the constructor) to be called, it was. This caused a new C++ object to be created whenever I wrapped an existing one. That object would then go float around in memory, as it was replaced by the wrapping function’s SetInternalField call.
Due to the way I was managing memory, this extra allocation of objects did not seem to cause a memory leak. Still, having an extra object floating around for an object’s lifespan did not sound good, so I had to find a way to fix it. And, finally, I did.
It did take a few hours, however, and, unfortunately, I was not on the clock at the time.
Update: As Matthias Ernst pointed out, my example was adding properties directly to the FunctionTemplate instead of to the Function Template’s instance template.
Update: The Second Way: Copy Constructor Method
Thanks to Christian Plesner Hansen for giving another way around this problem!
Instead of having two FunctionTemplates, you can use one, and just pass the constructor an argument pointing to the existing object:
Within the constructor you check if the first argument is an external, and if it is you don’t construct a Point:v8::Handle wrapPoint(Point *value) { v8::HandleScope scope; v8::Handle cons = pointConstructor->GetFunction(); v8::Handle external = v8::External::New(value); v8::Handle result = cons->NewInstance(1, &external); return scope.Close(result); }v8::Handle constructorCall(const v8::Arguments& args) { v8::HandleScope scope; v8::Handle external; if (args[0]->IsExternal()) { external = v8::Handle::Cast(args[0]); } else { int x = args[0]->Int32Value(); int y = args[1]->Int32Value(); Point *p = new Point(x, y); external = v8::External::New(p); } args.This()->SetInternalField(0, wrapper); return args.This(); }
Chrome’s Method
In web pages, most elements are not created using constructors. Instead, they are created using the document.createElement() function. One notable exception is the img element, which may also be created using a constructor.
Chrome handles this, from what I can tell by reading their code generators, etc., by running a script similar to the following in V8:
function Image() {
return document.createElementNS(
'http://www.w3.org/1999/xhtml', 'img');
};
ImageI think they do this so they can lazy-load the Image function, as many pages don’t use it at all.
I believe you got FunctionTemplate wrong. You need to work with the instance and prototype template instead of assigning properties to the function template itself. There is a direct equivalent to the JavaScript way:
JS: //make a constructor var MyClass = function(argument) { this.value = argument; } => Handle ft = FunctionTemplate::New(ConstructorCallback);
//add functions all instances should have MyClass.prototype.say = function() { alert(this.value); } => ft->PrototypeTemplate()->Set( String::New(“say”), FunctionTemplate::New(SayCallback));
//make an instance var testClass = new MyClass(“test”); testClass.say(); //alerts “test” => Local l = ft->NewInstance(1, test); Local::Cast(l->Get(“say”))->Call(l, 0, NULL);
Thanks for pointing that out. I was copying, pasting, and modifying Google’s example (in their embedding API page) and they were using ObjectTemplates. I was, it appears, a bit careless while doing so, and forgot to change that.
Thanks for the correction! Alex
Nice overview, Alex.
This is an issue that caused me many ours of debugging and hair pulling. I’m sure it will be very helpful to others.
Thanks. I do hope others will find it helpful.
I’m thinking of making one about persistent pointers, covering both the general overview (including the difference between Clear and Dispose that was cleared up for us) and the MakeWeak functionality. What do you think?
Alex
There is still one funny aspect to this: the “Point” function is actually a plain function (it constructs and returns a new object when it is actually supposed to initialize “this”) but you make it look like a constructor.
You could actually use it like so: “var point = Point(3, 4)”; whereas “new Point(3, 4)” will - create a new object - invoke Point(3, 4) on it - which will create and return a totally different object via wrapObject, becoming the result of the “new” expression - the object in step 1) is garbage collected
The fact that you can return something else than “this” from a JS constructor is surprising and I’m glad I finally understood what was itching me about your example.
That’s probably part of what confused me, actually. I wanted to use the function to initialize this, I think by using args.Holder(). Doing it that way, however, would require that I still use two separate FunctionTemplates, identical except that one actually has a C++ callback, whereas the other does not; otherwise, NewInstance() calls the constructor even when I’m trying to wrap an existing object (if there is another way around this, I’d like to hear it).
Note that I actually prevent it from being called without new:
Very nice overview!
The reason why it isn’t straightforward to use the same function as constructor and for creating wrappers is probably that you don’t need it in a browser — there you do ‘document.createElement(“div”)’ rather than ‘new DivElement()’. so there’s never been a need for it in v8. However, by using a small hack it is possible.
As you say, the problem when using the constructor for making wrappers for existing points is that it creates a new Point instance. One way to work around this for ‘wrapPoint’ to pass an argument to the constructor that tells it that there already is an instance: the External value containing the point.
v8::Handle wrapPoint(Point *value) { v8::HandleScope scope; v8::Handle cons = pointConstructor->GetFunction(); v8::Handle external = v8::External::New(value); v8::Handle result = cons->NewInstance(1, &external); return scope.Close(result); }
Within the constructor you check if the first argument is an external, and if it is you don’t construct a Point:
v8::Handle constructorCall(const v8::Arguments& args) { v8::HandleScope scope; v8::Handle external; if (args[0]->IsExternal()) { external = v8::Handle::Cast(args[0]); } else { int x = args[0]->Int32Value(); int y = args[1]->Int32Value(); Point *p = new Point(x, y); external = v8::External::New(p); } args.This()->SetInternalField(0, wrapper); return args.This(); }
Note that now you can use the instance created by the constructor.
Thanks. I thought about passing an argument, but never thought about passing an external as an argument. And I suppose that’s exactly like C++ copy constructors too. I’ll probably update the article soon.
Your example is extremely confusing. There are variable introduced in your code snippets with no definitions. For example in ConstructorCall where did wrapper come from? There is no “Handle” type in V8 there is a Handle. However Handle has no definition for Cast(). Also in wrapPoint where did point_templ come from. How did wrapPoint obtain point_templ? point_templ is not defined as a global. You also altered the name of the point constructor function which didn’t match. You use name “constructPoint” in FunctionTemplate::New then followed the constructor function named “ConstructorCall”.
It is very frustrating reading your examples because you didn’t do a good job of explaining the introduction of variables. It also would be helpful to post the entire working code. Had you did that the snippets would have been correct and compilable. Can you please address the aforementioned issued and correct the snippets. That would be a big help to me to be able to get a working sample.
If I had time or still worked with V8, I would. Unfortunately, neither are true.
In my defense, this article was meant to be used for illustration purposes; the reader is supposed to be familiar with v8 and how it works; also, at the time, it was based on the definition of Point that was in the embedder’s guide; this definition may have since changed, I really don’t know. In any case, this was simply meant to expand on that in a rather abstract way. Still, it does look like I was a bit too lazy.
As for v8::Handle::Cast… I seem to remember it existing. It may be a mistake (like the constructPoint appears to be), but perhaps, if indeed it is not in V8, perhaps it used to be and was removed?
I’m sorry for the trouble you’ve had. Keep in mind that this was my first blog post, and is over a year and a half old.
Alex
Ok Alex,
I finally figured it out. I had to cobble my solution from piecing together other samples. In the final snippet you hang the data on the This() pointer but in the Google example they use the Holder(). I was trying to get the data from the Holder which has no slot set up for the data and thereby throws and exception.
The problem I’ve been having has been inconsistency among the various samples. You example is helpful but contributes to the inconsistency especially for newbies. I recalled you or your colleagues are working on a V8 Cookbook. That would be very helpful and help advance adoption of the V8 engine.
thx