tags:

views:

318

answers:

3

The official examples of exposing a Point class seem to assume that there will be a fixed number of instances of it in your program. It is not clear how new instances are allocated in the C++ code, when new is called in Javascript.

How would you expose a class that can have multiple instances? For example, an Image class:

var img1 = new Image( 640, 480 );
var img2 = new Image( 1024, 768 );

img1.clear( "red" );
img2.clear( "black" );
A: 

I don't know how to achieve this in V8 Js engine exactly, but as in the Python world, you can just do as following. your Image class:

class Image
{
public:
    Image(int w, int h);
    int Width(void) const;
};

write some wrapper functions and expose these functions to the Js world:

Image* Image_New(int w, int h) { return new Image(w, h); }
void Image_Delete(Image* pImage) { delete pImage; }
int Image_Width(const Image* pImage) { return pImage->Width(); }

add the following codes to your js file:

var Image = function (w, h) {
    this.image = new Image(w, h);
    this.Width = function() {
        return Image_Width(this.image);
    };
};

and now you can get you code work. Additionally, the codes above haven't take the Garbage collection mechanism into consideration, so pay some special attention to it.Sorry for my borken English!

Cliffwolf
What about exporting of functions defined?
Basilevs
Yes, python has a great API for this. That's why V8 is so confusing! Why is there no V8::FunctionTemplate::SetClassAllocator( AllocateFn, FreeFn )?
Steve Hanov
+2  A: 

You need to maintain the allocations when an object is instantiated from an exposed object in JavaScript. It's not V8's responsibility to determine how your objects are created and destroyed -- Not to mention that could have a lot of unintended side effects.

Okay, let's say that you have a class called Foo and you have a static method wrap on the class. "wrap" does as intended and wraps the class to be exposed to JavaScript.


Edit: I expanded everything out. Usually I have all this v8 nonsense typedef'd to some degree. So, in case you want a handy typedef'd v8, here ya go:

#define String_new v8::String::New
#define Context_new v8::Context::New

typedef v8::ObjectTemplate Object_template;
typedef v8::AccessorInfo Accessor_info;
typedef v8::String String;
typedef v8::Context Context;
typedef v8::FunctionTemplate Function_template;

typedef v8::Local<v8::String> Local_string;
typedef v8::Local<v8::Value> Local_value;
typedef v8::Local<v8::Object> Local_object;

typedef v8::HandleScope Handle_scope;
typedef v8::Context::Scope Scope;
typedef v8::Context::Scope Context_scope;

typedef v8::Handle<Object_template> Handle_object_template;
typedef v8::Handle<v8::Object> Handle_object;
typedef v8::Handle<v8::Value> Handle_value;
typedef v8::Handle<v8::String> Handle_string;
typedef v8::Handle<v8::Context> Handle_context;
typedef v8::Handle<Object_template> Handle_object_template;
typedef v8::Handle<v8::External> Handle_external;
typedef v8::Handle<v8::Function> Handle_function;
typedef v8::Handle<v8::Script> Handle_script;

typedef v8::Persistent<Object_template> Persistent_object_template;
typedef v8::Persistent<v8::Object> Persistent_object;
typedef v8::Persistent<v8::Context> Persistent_context;
typedef v8::Persistent<v8::Function> Persistent_function;
typedef v8::Persistent<v8::String> Persistent_string;

typedef v8::TryCatch Try_catch;

typedef v8::Exception Exception;
typedef v8::Arguments Arguments;

typedef void (Object_template_extension)(Handle_object_template*);

Alright, now down to the fun.

namespace {
v8::Persistent<Object_template> foo_tmpl;
}

v8::Handle<v8::Value> Foo::wrap(const Arguments& args)
{   
    v8::HandleScope handle_scope;

    Foo* foo = new Foo();

    // The whole create only once thing
    if (tmpl->IsEmpty()) {
        v8::Handle<v8::ObjectTemplate> result = create_template();
        result->SetInternalFieldCount(1);
        *tmpl = Persistent_object_template::New(result);
    }

    // Here's your magic
    OBJECT_MAINTAINER->add_object(foo);

    // assigning the template/setting internal field
    v8::Handle<v8::ObjectTemplate> templ = *tmpl;
    v8::Handle<v8::Object> result = templ->NewInstance();
    v8::Handle<v8::External> class_ptr = v8::External::New(foo);
    result->SetInternalField(0, foo);

    // Now we can store a reference to itself that way we use "this" 
    foo->self = v8::Persistent<v8::Object>::New(handle_scope.Close(result));
    return foo->self;
}

This is just a really close-minded method that could be approached from a multitude of directions. Whenever you expose a class, it has to be instantiated. So, you have to dynamically create that instance. Handle your memory like you want, but just clean up afterwards.

Anyhow, now you can expose what you want to v8.

some_template->Set(v8::String::New("Foo"), v8::FunctionTemplate::New(Foo::wrap));

Wahlah, multiple instances.

Justin Van Horne
Shouldn't tmpl be foo_templ?
graham.reeds
And Wahlah -> Voilà!
graham.reeds
This is a good answer, however I don't see how Foo is deleted anywhere. I believe you need a foo.MakeWeak() before you return. How does OBJECT_MAINTAINER know when to delete foo?
Steve Hanov
That's your responsibility. It can be anything. When you leave a certain context, when the execution is no longer valid -- I really don't know what the scope of your object longevity is, so these are just suggestions.
Justin Van Horne
+1  A: 

This is the best blog post I could find on exposing C++ objects to V8 Javascript. It goes into deeper detail and breaks it down into smaller steps with code snippets. Be warned: the code snippets have little inconsistencies and it took me several reads to understand. Reading my brief summary beforehand may help:

  1. Objects must be wrapped in V8 templates. Note: The Google sample uses ObjectTemplates, but the author explains why he prefers FunctionTemplates.
    1. Create a FunctionTemplate. Instances of this template have an internal field to store the memory address of the C++ object. They also get the class' accessor methods.
    2. Make a function wrapObject() that will wrap a C++ object in one of these FunctionTemplates.
  2. The constructor must also be wrapped in a (different) V8 template. A different template is used to avoid unwanted recursion. (A method of combining both templates into one is described at the end of the blog post.)
    1. Create another FunctionTemplate. This template simply connects the JavaScript global scope (where new will be called from) to the C++ constructor.
    2. Make the method that the template will call. This method actually uses the C++ new operator and calls the C++ class constructor. It then wraps the object by calling the wrapObject() method created in step 1.2.

Now, the memory allocated in step 2.2 must be deleteed some time. Update: The next blog entry, "Persistent Handles," covers this in detail.

My notes on the actual code alluded to in these blog posts:

  • The wrapPoint() method in the blog is actually analogous to the unwrap() method in the actual code; not wrap()
  • To find other common points between the code, search for: SetInternalFieldCount(0, constructorCall
  • The actual code seems to do memory management by using the MakeWeak() method to sett a callback method that does the cleanup.
Leftium