Wrapping a C++ class, revisited

503 views
Skip to first unread message

Lyndsey

unread,
Jul 8, 2013, 3:35:53 AM7/8/13
to v8-u...@googlegroups.com
I know that others have posted questions about this here and elsewhere.  However, I generally find either the question or the answer (or both) to be either too vague or too old (or both).

I simply want to wrap a C++ class so that it can be exposed in Javascript, like so:

var myPoint = new Point(3,4);
print(myPoint.x);  //Assume print is defined and works splendidly

The Point example, of course, is taken directly from the V8 Embedder's Guide.  However, I had to tweak it significantly just to get it to compile and run (Win x64 Debug).  I also used a 2009 blog post by Alex Iskander.  This StackOverflow posting that referred me there offers a little clarification but not much.

The specific problem I'm having is that a Point can be constructed fine but its member, x, cannot be accessed.  I set breakpoints in the C++ getter function called GetPointX but they are never reached.  The Javascript snippet above prints "undefined" instead of "3" as desired.  I think I must be missing something simple.  Perhaps I'm not associating pointTemplate or instance_templ back to something else, like the Point constructor or the global context?

Note: At the moment, I am not interested in memory management or even the ability to set x.  Or y.  I just want to get this part working as a minimal proof-of-concept.  Maybe a function (rather than a property) on the Point class would be my next step (i.e. myPoint.inverse() where x and y are swapped or something).  But I'm not there yet.

I've included as much code as I think is necessary below.  Please let me know if more is needed and I'd be happy to supply it.  Thanks in advance for your help!

//The Point C++ class
class Point {
   public:
    Point(int x, int y) : x_(x), y_(y) { }
    int x_, y_;
  };
 
//The method where I start doing V8 things.  Set up an Isolate, Context, etc.  Only the relevant parts are shown here.
bool MyTest::DoStuff(...)
{
...
Local<FunctionTemplate> constructor = FunctionTemplate::New(ConstructPoint);
context->Global()->Set(String::New("Point"), constructor->GetFunction());
 
pointTemplate = FunctionTemplate::New();
Local<ObjectTemplate> instance_templ = pointTemplate->InstanceTemplate();
instance_templ->SetInternalFieldCount(1);
instance_templ->SetAccessor(String::New("x"), GetPointX);
...
}
 
//The Point constructor function
void MyTest::ConstructPoint(const FunctionCallbackInfo<Value>& args)
{
//start a handle scope
HandleScope handle_scope;
 
//get an x and y
int x = args[0]->Int32Value();
int y = args[1]->Int32Value();
 
//generate a new point
Point *point = new Point(x, y);
 
//return the wrapped point
WrapPoint(point);
}
 
//The wrapper function
Handle<Object> MyTest::WrapPoint(Point *pointToWrap) 
{
    //enter a handle scope
    HandleScope handle_scope;
 
    //create a new point instance

    Local<Object> point_instance = pointTemplate->GetFunction()->NewInstance();
 
    //set that internal field
    point_instance->SetInternalField(0, External::New(pointToWrap));
 
    //to prevent the point_instance from being destroyed when its
    //scope handle_scope is, use the Close() function
    return handle_scope.Close(point_instance);
}
 
//The getter function for "somePoint.x" in Javascript.  The guts of this could be wrong.  Sadly, I am never getting here at all!
void MyTest::GetPointX(Local<String> name, const PropertyCallbackInfo<Value>& info)
{
    Local<Object> self = info.Holder();
    Local<External> wrap = Local<External>::Cast(self->GetInternalField(0));
    void* ptr = wrap->Value();
    int value = static_cast<Point*>(ptr)->x_;

    info.GetReturnValue().Set(Number::New(value));
}

 

Ben Noordhuis

unread,
Jul 8, 2013, 7:09:15 AM7/8/13
to v8-u...@googlegroups.com
There is a bug in your WrapPoint() function, it creates and wraps a
new (JS) Point instance rather than wrapping the instance that's
passed to your constructor. Make it wrap args.This() and things
should work okay.

Lyndsey

unread,
Jul 8, 2013, 12:45:02 PM7/8/13
to v8-u...@googlegroups.com
Thank you Ben.  I assume you mean something like what's being done here?  I think the problem is that I'm not using node, and that would be needed to do the following, correct?

point->Wrap(args.This());  //Where Wrap is available because the C++ Point class extends ObjectWrap (from node.h)

I would like to do this in "pure V8" without using node.  Is that possible?  Am I making things too difficult?

Stephan Beal

unread,
Jul 8, 2013, 12:57:25 PM7/8/13
to v8-u...@googlegroups.com
On Mon, Jul 8, 2013 at 6:45 PM, Lyndsey <lyndse...@gmail.com> wrote:
Thank you Ben.  I assume you mean something like what's being done here?  I think the problem is that I'm not using node, and that would be needed to do the following, correct?

point->Wrap(args.This());  //Where Wrap is available because the C++ Point class extends ObjectWrap (from node.h)

I would like to do this in "pure V8" without using node.  Is that possible?  Am I making things too difficult?

Wrapping classes can be easily done without node but it gets really tedious really quickly. There are several frameworks out there which can simplify this. Here's one example (out of many) which demonstrates a generic approach:


but that particular one was massively broken by recent v8 API changes, so i unfortunately cannot recommend it to you. Others on this list have authored such tools, many of which possibly still work since the (still ongoing) v8 overhaul started, and maybe one of them can suggest a generic solution for you.



--
----- stephan beal
http://wanderinghorse.net/home/stephan/

Ben Noordhuis

unread,
Jul 8, 2013, 1:07:49 PM7/8/13
to v8-u...@googlegroups.com
On Mon, Jul 8, 2013 at 6:45 PM, Lyndsey <lyndse...@gmail.com> wrote:
> Thank you Ben. I assume you mean something like what's being done here? I
> think the problem is that I'm not using node, and that would be needed to do
> the following, correct?
>
> point->Wrap(args.This()); //Where Wrap is available because the C++ Point
> class extends ObjectWrap (from node.h)
>
> I would like to do this in "pure V8" without using node. Is that possible?
> Am I making things too difficult?

Not at all. In the snippet you posted, replace the call to
WrapPoint() with `args.This()->SetInternalField(0,
External::New(point));` and you should be good.

You may want to add args.IsConstructCall() and
constructor->HasInstance() checks to ensure that your constructor is
being invoked the way you expect it to.

In node.js we sidestep that issue by never exposing native code
directly to the user, there is always a JS shim in between. That may
or may not work for you, YMMV. Node.js has the luxury of being
allowed to assume that the executed JS code isn't hostile, unlike e.g.
a browser.

Stephan Beal

unread,
Jul 8, 2013, 1:38:41 PM7/8/13
to v8-u...@googlegroups.com
On Mon, Jul 8, 2013 at 7:07 PM, Ben Noordhuis <in...@bnoordhuis.nl> wrote:
or may not work for you, YMMV.  Node.js has the luxury of being
allowed to assume that the executed JS code isn't hostile, unlike e.g.
a browser.

Well worded: "isn't hostile" rather than "is safe" ;)

-- 

Lyndsey

unread,
Jul 8, 2013, 5:51:53 PM7/8/13
to v8-u...@googlegroups.com
Ok, this helped me get back on track.  As suggested, I replaced the call to WrapPoint in the ConstructPoint method with this:

args.This()->SetInternalField(0, External::New(point));

At runtime, this yielded the following error:

# Fatal error in v8::Object::SetInternalField()
# Internal field out of bounds

because I needed the following line in the DoStuff method:

constructor->InstanceTemplate()->SetInternalFieldCount(1);

In other words, I was doing things to pointTemplate's InstanceTemplate that I should have been doing to the constructor's InstanceTemplate.  I think I got this idea from some outdated postings about maintaining two templates instead of one, although there is plenty of newer info out there to suggest otherwise (shrug).  In any case, this makes a lot of sense and ties back to my original suspicion about the pointTemplate variable (and its InstanceTemplate) not really "going anywhere".

Thank you all very much for setting me straight.  The full updated code snippets are below.  I will update again if/when I get a Point function working as well.

//The Point C++ class
class Point {
   public:
    Point(int x, int y) : x_(x), y_(y) { }
    int x_, y_;
  };
 
//The method where I start doing V8 things.  Set up an Isolate, Context, etc.  Only the relevant parts are shown here.
bool MyTest::DoStuff(...)
{
...
 Local<FunctionTemplate> constructor = FunctionTemplate::New(ConstructPoint);
 constructor->InstanceTemplate()->SetInternalFieldCount(1);  //Set the InternalFieldCount on the CONSTRUCTOR'S InstanceTemplate
 constructor->InstanceTemplate()->SetAccessor(String::New("x"), GetPointX);  //Same goes for the Accessor
 context->Global()->Set(String::New("Point"), constructor->GetFunction());
...
}
 
//The Point constructor function
void MyTest::ConstructPoint(const FunctionCallbackInfo<Value>& args)
{
//start a handle scope
HandleScope handle_scope;
 
//get an x and y
int x = args[0]->Int32Value();
int y = args[1]->Int32Value();
 
//generate a new point
Point *point = new Point(x, y);
 
//Set the C++ point to the JS point
args.This()->SetInternalField(0, External::New(point));
}
 
//The getter function for "somePoint.x" in Javascript.  Sadly, I am never getting here!

Lyndsey

unread,
Jul 8, 2013, 5:58:00 PM7/8/13
to v8-u...@googlegroups.com
I should also note that I forgot to change the comment above the GetPointX method.  I am obviously reaching breakpoints in there now.  :)

Also, I wholeheartedly agree about adding the check to ensure the constructor is being called "properly".  I assume that will prevent Javascripters from constructing a Point with too few, too many, or incorrectly-typed arguments?


On Monday, July 8, 2013 2:35:53 AM UTC-5, Lyndsey wrote:

Ben Noordhuis

unread,
Jul 9, 2013, 12:12:06 PM7/9/13
to v8-u...@googlegroups.com
On Mon, Jul 8, 2013 at 11:58 PM, Lyndsey <lyndse...@gmail.com> wrote:
> I should also note that I forgot to change the comment above the GetPointX
> method. I am obviously reaching breakpoints in there now. :)
>
> Also, I wholeheartedly agree about adding the check to ensure the
> constructor is being called "properly". I assume that will prevent
> Javascripters from constructing a Point with too few, too many, or
> incorrectly-typed arguments?

That too but what I meant was guarding against non-constructor calls,
i.e. `MyConstructor()` rather than `new MyConstructor()`, or
construction as a different type:

function TheirConstructor() {}
TheirConstructor.prototype = new MyConstructor;
new TheirConstructor; // is-a MyConstructor but doesn't have
MyConstructor's internal fields

The above is why MyConstructor's native methods need to check on entry
that args.This() is of the expected type because it won't be when it's
part of a prototype chain (but see e.g.
args.This()->FindInstanceInPrototypeChain().)

Richard S

unread,
Jul 20, 2013, 9:45:46 AM7/20/13
to v8-u...@googlegroups.com
Hello. I've tried following along with this and the embedder's guide, but my point binding only works to an extent. The constructor works, but for some reason when I use an invalid constructor the object in JavaScript is not undefined (I set the return value to be so). Also, whenever I retrieve values from accessors, I get numbers that aren't even close to what they should be.

Here's my binding class: binder.cpp
And here's sample output: sample i/o

Stephan Beal

unread,
Jul 20, 2013, 10:40:43 AM7/20/13
to v8-u...@googlegroups.com
On Sat, Jul 20, 2013 at 3:45 PM, Richard S <blak...@gmail.com> wrote:
Hello. I've tried following along with this and the embedder's guide, but my point binding only works to an extent. The constructor works, but for some reason when I use an invalid constructor the object in JavaScript is not undefined (I set the return value to be so).

The return value of a JS constructor is ignored - it is handled automatically by the 'new' operator:

[stephan@host:~/]$ cat foo.js

function MyClass(){
    return "hi";
}

var x = new MyClass();
print(x instanceof MyClass);
[stephan@host:~/]$ js foo.js
true


Richard S

unread,
Jul 20, 2013, 11:12:01 AM7/20/13
to v8-u...@googlegroups.com, sgb...@googlemail.com
Okay, then is there a way to return an undefined object on an improper constructor? And I still have yet to solve the problem with values not being what they're supposed to be :/

Stephan Beal

unread,
Jul 20, 2013, 11:16:03 AM7/20/13
to v8-u...@googlegroups.com
On Sat, Jul 20, 2013 at 5:12 PM, Richard S <blak...@gmail.com> wrote:
Okay, then is there a way to return an undefined object on an improper constructor? And I still have yet to solve the problem with values not being what they're supposed to be :/

To signal a bad constructor, throw a JS-side exception by calling v8::ThrowException().

Richard Selneck

unread,
Jul 20, 2013, 11:20:43 AM7/20/13
to v8-u...@googlegroups.com
Ahh, okay. Thank you for that bit!


--
--
v8-users mailing list
v8-u...@googlegroups.com
http://groups.google.com/group/v8-users
---
You received this message because you are subscribed to a topic in the Google Groups "v8-users" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/v8-users/xuR2iL1NoWI/unsubscribe.
To unsubscribe from this group and all its topics, send an email to v8-users+u...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Reply all
Reply to author
Forward
0 new messages