Pull to refresh

Использование V8, часть 2

Website development *
Использование V8

Часть 2. Темплейты объектов, уведомления при уничтожении и пр.

Часть 1 здесь: krovosos.habrahabr.ru/blog/72474



— Темплейты объектов V8

Иногда мы не хотим создавать javascript-объект «с нуля». Иногда мы хотим, чтобы объект V8 сразу давал нам некоторый функционал. Например, мы хотим, чтобы объект имел методы и сразу был связан с каким-то нашим набором функций C++ или «оборачивал» какой-то класс C++.
Также возможно мы хотим связать с этим объектом V8 какие-то свои указатели (внешние для V8). Например, указатель на связанный экземпляр класса C++.

Для этого нужно использовать темплейты объектов (класс ObjectTemplate). Создавая объект V8 на базе темплейта мы сразу начиняем его неким функционалом. Для каждого из типов объектов, которые мы хотим создавать позднее в runtime период, мы должны создать темплейт (и сохранить его в постоянный хэндл V8). Позднее при создании объектов мы будем указывать этот темплейт.

Предположим, что у нас есть C++ класс, который оборачивает базу данных (например, SQLITE) следующим образом:

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.


Допустим, мы хотим иметь объект V8, который оборачивает объект dblite. Допустим мы хотим, чтобы объект V8 предоставлял методы open(), close(), execute() и свойство errorCode.

Для этого вначале придется написать некий вспомогательный код.

Прежде всего нужно будет забирать из объекта V8 связанные ссылки с объектами C++. При создании темплейта мы указываем сколько именно ссылок мы хотим хранить (это будет показано позднее) и объект V8 создает внутри себя обычный массив элементов void*. Затем мы можем обратиться к его элементам, забрать void* и преобразовать в нужный нам класс C++. Например, если мы знаем что в нулевой ячейке массива внешних ссыылок объекта V8 хранится указатель на класс C++ dblite, можно получить его так:

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.


Далее нужно объявить в своем C++ коде функции обратного вызова. То бишь функции C++, которые будут вызываться из V8. Пока рассмотрим два вида подобного вызова.

Обратный вызов из V8 в C++ для получения значения свойства (property) объекта:

Handle<Value> SomeProperty(Local<String> name,const AccessorInfo& info)
{
// info.Holder() указывает на объект V8 (класс Handle<Object>)
// name это имя свойства, к которому обращаются
 ...
}


* This source code was highlighted with Source Code Highlighter.


Обратный вызов из V8 в C++, который происходит при вызове метода объекта V8:

Handle<Value> SomeMethod(const Arguments& args)
{
// args.This() указывает на объект V8 (класс Handle<Object>)
// args (в целом) это массив переданных значений
...
}

* This source code was highlighted with Source Code Highlighter.

Например, для свойства errorCode функция обратного вызова могла бы выглядеть так:

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.


А для метода open() функция обратного вызова могла бы выглядеть так:

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.


Надеюсь, вы уже можете написать аналогичные функции Close() и Execute() для связи с методами dblite::open() и dblite::execute()?

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.


Вообщем, мы уже готовы создать темплейт для объекта V8, который оборачивает 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.


Небольшие пояснения. String::NewSymbol() использует хэш имен и работает быстрее String::New(), если используются зараннее известные имена.
Функция в темплейте объекта прописывается через темплейт функции (FunctionTemplate).

Теперь мы уже готовы написать функцию, которая создает объект V8 на базе dblite? Не вполне. А как же быть с временем жизни подобного объекта? Если мы сделаем его постоянным — он никогда не уничтожится. Временным — он уничтожится сборщиком мусора и вместе с ним «утечет» память, так как связанный экземпляр объекта dblite не будет уничтожен.

Итак, плавно переходим к следующей части.

— Получение уведомлений при уничтожении объектов V8

Как упоминалось в первой части, время жизни объекта V8 управляется самим V8 на основании счетчиков использования.
Получается, что постоянный хэндл никогда не освободит объект для V8. А временный будет уничтожен самим V8, но мы про это никак не узнаем.

Для выхода из этой ситуации V8 использует концепцию «слабо-связных» ссылок. Объект V8 можно пометить как «слабо-связанный» и передать функцию уведомления для обратного вызова. В какой-то момент времени сборщик мусора заметит, что на объект V8 ссылаются только «слабо-связанные» объекты и вызовет переданную функцию.

Объект делается «слабо-связанным» функцией MakeWeak:

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

* This source code was highlighted with Source Code Highlighter.


Функция обратного вызова уведомления о «полной слабо-связанности» объекта для нашего случая может выглядеть так:

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.


Вот теперь мы уже готовы написать код для создания объекта V8, оборачивающего 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.


Все замечательно, но как из кода javascript «добраться» до функции CreateDblite? Необходимо при создании контекста в его темплейт (да, да у контекстов тоже могут быть темплейты) добавить соответствующий метод.

Tags:
Hubs:
Total votes 29: ↑26 and ↓3 +23
Views 4.4K
Comments Comments 14