📜 ⬆️ ⬇️

Using V8 Part 2

Using V8

Part 2. Object templates, notifications on destruction, etc.

Part 1 here: krovosos.habrahabr.ru/blog/72474
')


- V8 object templates

Sometimes we don’t want to create a javascript object from scratch. Sometimes we want a V8 object to immediately give us some functionality. For example, we want the object to have methods and immediately be associated with some of our set of C ++ functions or “wrap” some C ++ class.
It is also possible that we want to associate some of our own pointers with this V8 object (external to V8). For example, a pointer to an associated instance of the C ++ class.

To do this, use the templates of objects (class ObjectTemplate). Creating a V8 object on the basis of the template, we immediately fill it with some functionality. For each of the object types that we want to create later in the runtime period, we need to create a template (and save it to a permanent V8 handle). Later, when creating objects, we will specify this template.

Suppose we have a C ++ class that wraps the database (for example, SQLITE) as follows:

class dblite
{
bool open( const char * name);
void close();
bool execute( const char * sql);
int error_code();
};


* This source code was highlighted with Source Code Highlighter .


Suppose we want a V8 object that wraps a dblite object. Suppose we want the V8 object to provide the open (), close (), execute () methods and the errorCode property.

To do this, you first have to write some auxiliary code.

First of all, you will need to take away from the V8 object the associated links with C ++ objects. When creating a template, we specify how many links we want to store (this will be shown later) and the V8 object creates an ordinary array of void * elements inside it. Then we can refer to its elements, pick up void * and convert it to the C ++ class we need. For example, if we know that a pointer to a C ++ dblite class is stored in the zero cell of the array of external links of a V8 object, we can get it like this:

dblite* unwrap_dblite(Handle<Object> obj)
{
Handle<External> field = Handle<External>::Cast(obj->GetInternalField(0));
void * ptr = field->Value();
return static_cast<dblite*>(ptr);
}


* This source code was highlighted with Source Code Highlighter .


Next, you need to declare a callback function in your C ++ code. I mean, the C ++ functions that will be called from V8. For now, consider two types of such a call.

Callback from V8 to C ++ to get the value of a property (property) of an object:

Handle<Value> SomeProperty(Local< String > name, const AccessorInfo& info)
{
// info.Holder() V8 ( Handle<Object>)
// name ,
...
}


* This source code was highlighted with Source Code Highlighter .


Callback from V8 to C ++, which occurs when you call a method on a V8 object:

Handle<Value> SomeMethod( const Arguments& args)
{
// args.This() V8 ( Handle<Object>)
// args ( )
...
}

* This source code was highlighted with Source Code Highlighter .

For example, for the errorCode property, the callback function would look like this:

Handle<Value> ErrorCode(Local< String > name, const AccessorInfo& info)
{
// Handle<Object>, dblite, error_code(),
// V8 ( ),
return Integer::New(unwrap_dblite(info.Holder())->error_code());
}


* This source code was highlighted with Source Code Highlighter .


And for the open () method, the callback function would look like this:

Handle<Value> Open( const Arguments& args)
{
if (args.Length() < 1) return Undefined(); // ? !
dblite* db = unwrap_dblite(args.This()); // dblite
string sql = to_string(args[0]); // C++, to_string
return Boolean::New(db->open(sql.data())); // open()
}


* This source code was highlighted with Source Code Highlighter .


I hope you can already write similar functions Close () and Execute () to communicate with the dblite :: open () and dblite :: execute () methods?

Handle<Value> Close( const Arguments& args)
{
// !
}


* This source code was highlighted with Source Code Highlighter .


Handle<Value> Execute( const Arguments& args)
{
// !
}


* This source code was highlighted with Source Code Highlighter .


In general, we are ready to create a template for a V8 object that wraps dblite:

Handle<ObjectTemplate> CreateDbLiteTemplate()
{
HandleScope handle_scope;
Local<ObjectTemplate> result = ObjectTemplate::New(); //
result->SetInternalFieldCount(1); // ( V8) (void*)
// dblite*

// ( ) - ?
result->SetAccessor( String ::NewSymbol( "errorCode" ), ErrorCode); //

// -
result->Set( String ::NewSymbol( "open" ), FunctionTemplate::New(Open));
result->Set( String ::NewSymbol( "close" ), FunctionTemplate::New(Close));
result->Set( String ::NewSymbol( "execute" ), FunctionTemplate::New(Execute));

// , HandleScope
// "" HandleScope - handle_scope
return handle_scope.Close(result);
}


* This source code was highlighted with Source Code Highlighter .


Small explanations. String :: NewSymbol () uses a hash of names and works faster than String :: New () if known names are used in advance.
A function in an object's template is written through a function template (FunctionTemplate).

Now we are ready to write a function that creates a dblite V8 object? Not quite. But what about the lifetime of such an object? If we make it permanent, it will never be destroyed. Temporary - it is destroyed by the garbage collector and memory will “leak” with it, since the associated instance of the dblite object will not be destroyed.

So, smoothly proceed to the next part.

- Receive notification when destroying V8 objects

As mentioned in the first part, the lifetime of a V8 object is controlled by the V8 itself based on usage counters.
It turns out that a permanent handle will never free an object for a V8. A temporary will be destroyed by the V8 itself, but we will not know about it.

To get out of this situation, V8 uses the concept of "weakly connected" links. The V8 object can be marked as “loosely coupled” and passed to the notification function for the callback. At some point in time, the garbage collector will notice that only “weakly-connected” objects refer to the V8 object and will call the passed function.

The object is made “weakly bound” by the MakeWeak function:

void Persistent<T>::MakeWeak( void * parameters, WeakReferenceCallback callback);

* This source code was highlighted with Source Code Highlighter .


The callback function of the notification about the “complete weakly connectedness” of an object for our case may look like this:

void DbLiteWeakCallback(Persistent<Value> object , void * parameter)
{
dblite* db = (dblite*) parameter; // parameters MakeWeak, dblite*
delete db; // C++
object .ClearWeak(); // "-",
object .Dispose(); // ,
}

* This source code was highlighted with Source Code Highlighter .


Now we are ready to write code to create a V8 object wrapping a dblite.

Persistent<ObjectTemplate> DbLite_template; //

Persistent<Object> CreateDbLite( const char * db_name)
{
Persistent<Object> obj;
// -
if (DbLite_template.IsEmpty()) DbLite_template = Persistent<ObjectTemplate>::New(CreateDbLiteTemplate());
// c++
dblite* db = new dblite; if (db_name) db->open(db_name);
// js
obj = Persistent<Object>::New(DbLite_template->NewInstance());
// "-", parameter dblite*
obj.MakeWeak(db, DbLiteWeakCallback);
// js c++
obj->SetInternalField(0, External::New(db));
return obj;
}

* This source code was highlighted with Source Code Highlighter .


Everything is great, but how to get from the javascript code to the CreateDblite function? It is necessary when creating a context in its template (yes, yes, contexts can also have templates) add the appropriate method.

Source: https://habr.com/ru/post/72592/


All Articles