v8/node class embedding problems

310 views
Skip to first unread message

Laurie Harper

unread,
May 19, 2011, 4:31:39 PM5/19/11
to v8-u...@googlegroups.com
Hi, I posted these issues first to the Node mailing list as the errors I'm seeing are coming from assertions in Node's API, but they appear to be a direct result of how the v8 JavaScript <-> C++ bridge works, so I'm bringing them here for further clarification. I'll include the full stripped-down C++ code I'm testing with below.

So far I have two problems, both of which I think stem from the same underlying issue. The first is that if I set up accessors on the prototype of my c++ proxy I'm unable to unwrap the internal field object; switching from t->PrototypeTemplate()->SetAccessor() to t->InstanceTemplate()->SetAccessor() resolved the problem (see commented line in Initialize() in the code below. Strangely, setting a method on the prototype seems to work just fine. So, question: why does it not work to set an accessor on the prototype?

The second problem comes when I try to set up inheritance in JavaScript from an object defined in c++. I think I understand why this one fails, but have no idea how to fix it... I'm using test code like this, where Vector is defined in c++ as show below:

function f() {}; f.prototype = Vector.prototype;
function g() { Vector.apply(this, arguments) }
var v = new g(1,2,3);

That emulates a common pattern for inheritance in JavaScript to avoid calling the base type constructor when setting up the prototype of the inheriting class. I think the problem is that, since there is no explicit 'new Vector' anywhere, the c++ instantiation logic isn't fired at the right time. The result is that Arguments::This() returns an object of an unexpected type (?) and wrapping/unwrapping the proxied object fails. So, the question is how to adjust my code to work when a constructor is called without the 'new' operator I think?

Here's the test code I'm working with:

*** Vector.h:

class Vector: public node::ObjectWrap {
public:
static Persistent<FunctionTemplate> ctor;
static void Initialize(Handle<Object> target);

static Handle<Value> GetX(Local<String> property, const AccessorInfo& info);
static void SetX(Local<String> property, Local<Value> value, const AccessorInfo& info);

protected:
static Handle<Value> New(const Arguments &args);
Vector(btScalar &x, btScalar &y, btScalar &z);

private:
btVector3* m_btVector3;
};

*** Vector.cc:

Persistent<FunctionTemplate> Vector::ctor;

void
Vector::Initialize(Handle<Object> target) {
HandleScope scope;

ctor = Persistent<FunctionTemplate>::New(FunctionTemplate::New(New));
ctor->InstanceTemplate()->SetInternalFieldCount(1);
ctor->SetClassName(String::NewSymbol("Vector"));

// ctor->PrototypeTemplate()->SetAccessor(String::NewSymbol("x"), GetX, SetX);
ctor->InstanceTemplate()->SetAccessor(String::NewSymbol("x"), GetX, SetX);

target->Set(String::NewSymbol("Vector"), ctor->GetFunction());
}

Handle<Value>
Vector::New(const Arguments &args) {
float x, y, z;
HandleScope scope;

if (args.Length() == 1 && args[0]->IsArray()) {
x = args[0]->ToObject()->Get(0)->NumberValue();
y = args[0]->ToObject()->Get(1)->NumberValue();
z = args[0]->ToObject()->Get(2)->NumberValue();
} else {
x = args[0]->IsNumber() ? args[0]->NumberValue() : 0;
y = args[1]->IsNumber() ? args[1]->NumberValue() : 0;
z = args[2]->IsNumber() ? args[2]->NumberValue() : 0;
}

Vector* obj = new Vector(x, y, z);
obj->Wrap(args.This());
return args.This();
}

Vector::Vector(btScalar &x, btScalar &y, btScalar &z) {
m_btVector3 = new btVector3(x, y, z);
}

Handle<Value>
Vector::GetX(Local<String> property, const AccessorInfo& info) {
HandleScope scope;
// Vector* v = ObjectWrap::Unwrap<Vector>(info.This()); // This vs. Holder?
Vector* v = ObjectWrap::Unwrap<Vector>(info.Holder()); // This vs. Holder?
Local<Number> result = Number::New(v->m_btVector3->getX());
return scope.Close(result);
}

void
Vector::SetX(Local<String> property, Local<Value> value, const AccessorInfo& info) {
Vector* v = ObjectWrap::Unwrap<Vector>(info.Holder());
v->m_btVector3->setX(value->NumberValue());
}

--
Laurie Harper
http://laurie.holoweb.net/

Laurie Harper

unread,
May 19, 2011, 8:22:13 PM5/19/11
to v8-u...@googlegroups.com
Oops, there's a line missing from the JavaScript inheritance setup code; it should read:

function f() {}; f.prototype = Vector.prototype;
function g() { Vector.apply(this, arguments) }

g.prototype = new f();


var v = new g(1,2,3);

It's correct in my test suite, so my questions still stand...

> --
> v8-users mailing list
> v8-u...@googlegroups.com
> http://groups.google.com/group/v8-users

Laurie Harper

unread,
May 19, 2011, 9:34:25 PM5/19/11
to v8-u...@googlegroups.com
I put all the code into a gist which can be downloaded, built and tested locally:

$ git clone git://gist.github.com/982157.git
$ cd 982157
$ node-waf configure build
$ node VectorTests.js
Vector is function Vector() { [native code] }
Vector.proto is [object Object]
v is { x: 1 }
v.x is 1
v is { x: 9 }
v.x is 9
Assertion failed: (handle->InternalFieldCount() > 0), function Wrap, file /Users/laurie/.nvm/v0.4.5/include/node/node_object_wrap.h, line 61.
Abort trap

L.

Laurie Harper

unread,
May 20, 2011, 12:42:02 AM5/20/11
to v8-u...@googlegroups.com
Added a comment to the gist with one possible work-around for the inheritance issue:


There must be a better way to deal with this though... Suggestions welcome.

L.

Laurie Harper

unread,
May 20, 2011, 1:00:59 AM5/20/11
to v8-u...@googlegroups.com
Never mind, that doesn't solve anything, just masks some of the problems.

Stephan Beal

unread,
May 21, 2011, 8:06:49 AM5/21/11
to v8-u...@googlegroups.com
On Fri, May 20, 2011 at 3:34 AM, Laurie Harper <lau...@holoweb.net> wrote:
Assertion failed: (handle->InternalFieldCount() > 0), function Wrap, file /Users/laurie/.nvm/v0.4.5/include/node/node_object_wrap.h, line 61.

When you subclass a native class from JS then the native 'this' pointer (which is stored in an internal field) cannot, without extra tooling to make it work, be found. The problem is that the JS 'this' is a subclass with no internal field count. This can be worked around on the C++ side, so that such lookups will (with a bit of extra effort) be able to find their proper native "this" object.

i have an example which does this type of lookup here:


in the function FindHolder(). That function is used internally by my API to do the "this" lookup when a C++/JS-bound class is subclassed from JS. Because this requires a significant performance overhead for the lookup, it is a compile-time (template-defined) option (in my lib, anyway - feel free to implement yours how you like).

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

Laurie Harper

unread,
May 22, 2011, 7:32:38 PM5/22/11
to v8-u...@googlegroups.com
Thanks for the link; unfortunately I don't think it solves this problem; with the code I posted, the internal field count seems to be 0 on every object in the prototype chain, so that could would never find/return a suitable object.
Reply all
Reply to author
Forward
0 new messages