Recovering a FunctionTemplate (or how to ->Inherit from one you've lost the handle to?)

68 views
Skip to first unread message

Rodrigo Hernandez

unread,
Oct 10, 2020, 4:44:50 PM10/10/20
to v8-users
Hello,

So, suppose I am wrapping a C++ class around a JS constructor, I've already created and initialized the isolate, created and initialized a context, I have the FunctionTemplate for the constructor and I have instantiated the function and I can call it from Js as x() or new X(),
all this is fine, the function template context is finished and the handle lost.

But then I want to create another wrapped object for a class that inherits from the first one.
Is there a way to retrieve the base class FunctionTemplate so in the child class I can call child->Inherit(base)?

Is there another way of doing this?

Right now I am keeping a C++ unordered_map of isolate to persistent handle to function templates, and that works, but is that the only option?

Thanks!

Ben Noordhuis

unread,
Oct 11, 2020, 3:49:10 AM10/11/20
to v8-users
It's not the only option but it's a pretty good solution. Less optimal
solutions:

- store them in the snapshot with SnapshotCreator::AddData() (but you
can only retrieve it once)
- scan the heap for them with Isolate::VisitHandlesWithClassIds().

No doubt there are more ways to stow them away somewhere.

Rodrigo Hernandez

unread,
Oct 11, 2020, 1:40:27 PM10/11/20
to v8-users
Thanks Ben, I see how those are less optimal, I was hoping for something along the lines of context->global()->GetFunctionTemplate("constructor"), or something along those lines.
The only problem I have with my approach is that I need a Finalize function on each wrapped class that pretty much just resets the permanent handles.

Thanks Again!

Rodrigo Hernandez

unread,
Oct 20, 2020, 12:40:00 AM10/20/20
to v8-users
So, coming back to this, is there a way to cast/convert/wrap a local template (function or object) into a v8::Value?
I am thinking about hiding them inside an internal field so I could do:

context->global()->GetFunction("constructor")->GetInternalField(X).

how safe would it be to cast them to a void* and wrapping them inside a v8::External?

Ben Ernst

unread,
Oct 21, 2020, 7:39:59 PM10/21/20
to v8-users
Rodrigo, you should be able to trivially cast a v8::Function or v8::Object to v8::Value is that what you're trying to do? In that case you don't need to put them in a v8::External. You can pass those objects to data->SetInternalField safely I would think.
Ben

Rodrigo Hernandez

unread,
Oct 22, 2020, 4:05:07 PM10/22/20
to v8-users
Hi Ben,

Yes, however what I need to store is the v8::FunctionTemplate, v8::Local<v8::FunctionTemplate> to be more specific, which does not inherit from Value and there is a static_assert that checks for that.

So perhaps, maybe if go to the real problem a different solution may arise.

I have a Js wrapper C++ base class from which all C++-to-Js must inherit, this is based on the code for Node.js, so when I write a new class that inherits from this wrapper, I need to allocate a FunctionTemplate on the isolate/context pair, like so:

I removed some lines, but otherwise you can look at this code here: https://github.com/AeonGames/AeonGUI/blob/master/core/dom/EventTarget.cpp#L23

    void EventTarget::Initialize ( v8::Isolate* aIsolate )
    {
        v8::Local<v8::Context> context = aIsolate->GetCurrentContext();
        //---------------------------------------
        // Store constructor on a callback data object
        v8::Local<v8::ObjectTemplate> constructor_data_template = v8::ObjectTemplate::New ( aIsolate );
        constructor_data_template->SetInternalFieldCount ( 1 );
        v8::Local<v8::Object> constructor_data =
            constructor_data_template->NewInstance ( context ).ToLocalChecked();

        // Prepare EventTarget constructor template
        v8::Local<v8::FunctionTemplate> constructor_template = v8::FunctionTemplate::New ( aIsolate, JsObjectWrap::New<EventTarget>, constructor_data ); // <----- THIS IS THE FunctionTemplate I NEED TO STORE
        constructor_template->SetClassName ( v8::String::NewFromUtf8 ( aIsolate, "EventTarget" ).ToLocalChecked() );
        constructor_template->InstanceTemplate()->SetInternalFieldCount ( 1 );

        AddFunctionTemplate ( aIsolate, typeid ( EventTarget ), constructor_template ); // <---- THIS IS MY CURRENT SOLUTION

        //----------------------------------------------------------------

        v8::Local<v8::Function> event = event_template->GetFunction ( context ).ToLocalChecked();
        context->Global()->Set ( context, v8::String::NewFromUtf8 (
                                     aIsolate, "Event" ).ToLocalChecked(),
                                 event ).FromJust();

        v8::Local<v8::Function> constructor = constructor_template->GetFunction ( context ).ToLocalChecked();
        constructor_data->SetInternalField ( 0, constructor );
        context->Global()->Set ( context, v8::String::NewFromUtf8 (
                                     aIsolate, "EventTarget" ).ToLocalChecked(),
                                 constructor ).FromJust();
    }

Then, when I want to have Node inherit from EventTarget I write a similar static function (the previous one is also static)

    void Node::Initialize ( v8::Isolate* aIsolate )
    {
        if ( HasFunctionTemplate ( aIsolate, typeid ( Node ) ) )
        {
            throw std::runtime_error ( "Isolate already initialized." );
        }

        v8::Local<v8::Context> context = aIsolate->GetCurrentContext();

        // Prepare Node constructor template
        v8::Local<v8::FunctionTemplate> constructor_template = v8::FunctionTemplate::New ( aIsolate );
        constructor_template->SetClassName ( v8::String::NewFromUtf8 ( aIsolate, "Node" ).ToLocalChecked() );
        constructor_template->Inherit ( EventTarget::GetFunctionTemplate ( aIsolate, typeid ( EventTarget ) ) ); // <-- THIS IS WHAT I REALLY NEED

        AddFunctionTemplate ( aIsolate, typeid ( Node ), constructor_template );

        v8::Local<v8::Function> constructor = constructor_template->GetFunction ( context ).ToLocalChecked();
        context->Global()->Set ( context, v8::String::NewFromUtf8 (
                                     aIsolate, "Node" ).ToLocalChecked(),
                                 constructor ).FromJust();
    }

As I said before, this works but I feel that makes the code too verbose and prone to error, for example now I am now forced to have a trivial Finalize function on all classes:

    void Node::Finalize ( v8::Isolate* aIsolate )
    {
        RemoveFunctionTemplate ( aIsolate, typeid ( Node ) );
    }

because the FunctionTemplate objects are stored as Persistent Handles in this map:

static std::unordered_map<std::pair<v8::Isolate*, size_t>, v8::Persistent<v8::FunctionTemplate, v8::CopyablePersistentTraits<v8::FunctionTemplate>>> FunctionTemplates{};

So I need to free them all, unlike a Local.

So, perhaps there is a way where I can either store the template in the constructor, or maybe there is a way to have Node inherit from EventTarget without requiring the FunctionTemplate, but instead require just the function?

Thanks for taking a look!

Ben Ernst

unread,
Oct 23, 2020, 2:16:09 AM10/23/20
to v8-users
I work with embedded V8, not with node, so some of that is french to me :)
I tried storing FunctionTemplate in a Value and encountered the same error.
If you're keeping the persistent handles in a global collection in any case, you might consider taking a raw pointer to the Persistent, and storing that in a v8::External. Then you can static_cast it back to FunctionTemplate where you call "Inherit". It still means you're doing some deliberate garbage handling however.

Rodrigo Hernandez

unread,
Oct 23, 2020, 8:01:13 PM10/23/20
to v8-users
Yeah, that sounds like needlessly complicating things even more. I'll just stash this problem for now since its already working.

Thanks again!

Reply all
Reply to author
Forward
0 new messages