Pull to refresh

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

Website development *
V8 — это движок JavaScript от Google, который используется в браузере Chrome. Он быстрый и доступен в исходных кодах (С++) для Linux (точнее для gcc) и под Windows.

В свете роста популярности использования V8 я решил поделиться своим (годичным) опытом его использования на платформе Windows в качестве серверного скриптового движка.

Часть 1. Введение и простейшая программа, использующая V8.


— Установка, сборка и добавление в свой проект

Итак, V8 — быстрый движок JavaScript (ECMA-262) от Google, используемый в браузере Chrome:

code.google.com/p/v8

V8 доступен в исходных кодах по лицензии BSD:

code.google.com/intl/ru-RU/apis/v8/terms.html

Я расскажу как его собрать под MS Visual Studio Express Edition 2008. Установка под Windows описана здесь:

code.google.com/p/v8/wiki/BuildingOnWindows

Итак, устанавливаем:

python

www.python.org/download/windows

scons

www.scons.org/download.php

subversion

например, www.sliksvn.com/pub/Slik-Subversion-1.6.5-win32.msi

Затем добавляем в PATH пути к бинарникам python, scons и subversion.

Скачиваем текущие исходники V8 из ветки trunk (стабильной):

svn checkout v8.googlecode.com/svn/trunk v8

В папке tools\visual studio находятся проекты V8 для Visual Studio.
В них удобно изучать код, но собирать V8 лучше из командной строки (из корневой папки где SConstruct), например, так:

scons msvcltcg=off mode=release library=static snapshot=on env="INCLUDE:C:\Program Files\Microsoft SDKs\Windows\v6.0A\Include;C:\Program Files\Microsoft Visual Studio 9.0\VC\include,LIB:C:\Program Files\Microsoft SDKs\Windows\v6.0A\Lib;C:\Program Files\Microsoft Visual Studio 9.0\VC\lib"


(пути к установке по умолчанию MS VS EE 2008).

msvcltcg это Link Time Optimization. Рекомендуется включать только для финальных релизов, иначе размер v8.lib увеличиться в 3-4 раза и скорость линковки вашего проекта упадет.

Как видно собираем мы статическую библиотеку v8.lib, которая скоро и появится в этой же папке.

Берем v8.h из папки include и v8.lib и добавляем к своему MS Visual Studio 2008 проекту.

Также не забудьте сделать в проекте:

#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "winmm.lib")


Иначе будут проблемы при линковке. Собственно, теперь движок V8 у нас доступен в проекте и можно начинать изучать Embedder's Guide

code.google.com/intl/ru-RU/apis/v8/embed.html

— Введение в использование V8

Открываем v8.h и изучаем.

V8 предоставляет свою функциональность с помощью C++ классов, которые объявлены в пространстве имен (namespace) v8. Все важные javascript-структуры обернуты с помощью соответствующего С++ класса. Например, класс v8::Integer представляет собой целое число (i64) в JavaScript.

v8.h

...
class Context;
class String;
class Value;
class Utils;
class Number;
class Object;
class Array;
class Int32;
class Uint32;
class External;
class Primitive;
class Boolean;
class Integer;
class Function;
class Date;
...


Самый важный класс — это контекст (v8::Context), то есть место, внутри которого компилируется и выполняется javascript-код. Фактически это же и место хранения глобального объекта javascript. Трудно представить взаимодействие с v8, которое не потребует контекста (но, наверное, можно).

Внутри себя V8 поддерживает свой собственный стек, где размещаются стековые объекты V8. Например, для использования контекста необходимо «войти в него». Для этого используется специальный класс C++ Context::Scope (подробнее ниже). Конструктор этого класса размещает на вершине стека V8 стековый объект V8 для входа в контекст. Вызов деструктора класса Context::Scope убирает соответствующий стековый объект с вершины стека V8. На стеке V8 размещаются также конструкции отлова исключений (TryCatch), пулы хранения временных хэндлов (HandleScope) и другие объекты V8. Все классы C++, которые управляют этими объектами, устроены одинаковы: в конструкторе размещаем объект на стек V8, в деструкторе — убираем.

V8 (как и любая javascript среда выполнения) содержит сборщик мусора, занимающийся удалением объектов V8, на которые никто не ссылается. Поэтому взаимодействие с объектами V8 из C++ кода происходит посредством хэндлов (v8::Handle), то бишь указателей на указатели. Упрощенно это можно представить так:

хэндл ---> ссылка ---> слот V8 (со счетчиком использования) --> объект V8

Реализованы хэндлы в виде template-классов C++: v8::Handle, v8::Local, v8::Persistent.
По времени жизни хэндлы делятся на временные (v8::Local) и постоянные (v8::Persistent).
Можно считать, что это аналоги C++ стековых и хиповых объектов, то есть время жизни временных хендлов привязано к стеку, а постоянные живут пока их явно не уничтожишь.

Далее я предполагаю, что мы находимся в пространстве имен v8 и STL («using namespace v8; using namespace std; „).

Итак, временные хэндлы V8 (v8::Local). Используются чаще всего. Сравним их время жизни со стековым объектом C++:

V8                                       C++
{                                        {
  HandleScope handle_scope;            
  Local<Integer> a = Integer::New(1);         int a = 1;
}                                         }
// a нельзя использовать


* This source code was highlighted with Source Code Highlighter.


Как видно, для хранения временных хендлов V8 понадобился объект HandleScope. Это стековый объект V8 для хранения тех самых ссылок на объекты V8. Упрощенно это аналог начала блока стековых переменных. Создание нового временного хендла всегда приводит к созданию ссылки в объекте HandleScope, который ближе всего к вершине стека (стека V8, разумеется). Нельзя создать временный хендл без HandleScope!

Вы будете использовать временные хендлы очень часто. Например, нам требуется преобразовать строку C++ в строку V8. Для строки в формате UTF-8 это делается следующим образом:

string some_utf8_string;
Local<String> s = String::New(some_utf8_string.data(), some_utf8_string.size());

* This source code was highlighted with Source Code Highlighter.


Многие классы (например, Integer, String, Boolean, Date) предоставляют статический метод New (возвращающий временный хэндл), который позволяет создать новый объект V8 этого класса и занести в него данные из C++. Например (см. v8.h):

class String
...
 static Local<String> New(const char* data, int length = -1);
...

* This source code was highlighted with Source Code Highlighter.


Однако, может возвращаться и объект типа Value:

...
class V8EXPORT Date : public Value {
public:
 static Local<Value> New(double time);
...

* This source code was highlighted with Source Code Highlighter.



Класс Value хранит произвольный тип V8. Это то, на что может указывать переменная javascript: число, строка, объект, функция, массив и пр. Value предоставляет методы для проверки своего типа (IsUndefined(), IsNull(), IsString(),IsFunction() и пр.). Преобразование Value в нужный тип осуществляется с помощью метода Cast интересующего нас класса. Например:

Local<Value> foo;
Local<Function> bar;

if (foo->IsFunction())
  bar = Handle<Function>::Cast(foo);

* This source code was highlighted with Source Code Highlighter.


В то же время Value является базовым классом C++ для классов Integer, String и пр. Поэтому можно делать так:

Local<Value> foo = Local<String> bar;

* This source code was highlighted with Source Code Highlighter.


— Постоянные хэндлы V8

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

Первый случай: контекст V8. Контекст мы создаем сами и уничтожаем, когда он больше не нужен.

Второй случай: объект V8 использует экземпляр объекта C++ и содержит внутри себя ссылку на этот экземпляр. Ясно, что если сборщик мусора вдруг решит подчистить объект V8, то мы потеряем ссылку на объект C++ и произойдет утечка памяти. Однако, мы хотим, чтобы объекты V8 уничтожались! Нам просто необходимо уведомление о том, что это сейчас произойдет.

Все это возможно с помощью постоянных хэндлов V8 (Persistent).

Я приведу опять код для V8 и аналог (в смысле времени жизни) кода на C++.

Persistent<Integer> p;                            int *p = 0;
{                                                 {
    HandleScope handle_scope;                    
    Local<Integer> a = Integer::New(1);             int a = 1;
    p = Persistent<Integer>::New(a);                 p = new int (a);
}                                                 }

Persistent<Integer> q = p;                        int* q = p;
p.Dispose();                                     delete p;
p.Clear();                                        p = 0;

int a = q->Int32Value();                         int a = *q;    
// ОШИБКА! Уже уничтожено!                        // ОШИБКА! Ссылка уничтожена!

* This source code was highlighted with Source Code Highlighter.


Итак, основной способ получения постоянного хэндла (из временного) — это использование вызова Persistent::New(). Context является единственным классом, который при своем создании (New) сразу возвращает постоянный хэндл. Надеюсь, всем уже понятно — почему он так делает.

Вопрос получения уведомлений при уничтожении постоянных хэндлов мы отложим, а пока попробуем соорудить первую рабочую программу, которая выполняет javascript.

Для этого нам потребуется извлекать строки из объекта String V8 в C++ код. Мы воспользуемся простейшим способом — классом String::AsciiValue.

// получить строковое представление Value; если это невозможно, вернуть пустую строку
string to_string(Local<Value> v)
{
    String::AsciiValue data(v);
    const char* p = *data;
    if (p == 0) return string();
    return string(p);
}

* This source code was highlighted with Source Code Highlighter.


Функция, которая выполняет произвольный javascript-код, будет выглядеть так:

string v8_exec(const char* javascript)
{
    HandleScope handle_scope;    // стековый объект для хранения временных хэндлов
    Persistent<Context> context = Context::New(); // создаем контекст и сохраняем его постоянный хэндл
    Context::Scope context_scope(context); // стековый объект "входа" в контекст; для компиляции и выполнения внутри контекста
    TryCatch try_catch; // стековый объект для отлова javascript-исключений
    Local<String> source = String::New(javascript); // преобразуем строку с кодом в строку V8
    Local<Script> script = Script::Compile(source); // компилируем
    if (try_catch.HasCaught()) throw to_string(try_catch.Message()->Get()); // ошибка компиляции? исключение бросаем
    Local<Value> result = script->Run(); // выполняем
    if (try_catch.HasCaught()) throw to_string(try_catch.Message()->Get()); // ошибка выполнения? исключение бросаем
    context.Dispose(); // уничтожаем контекст - больше не нужен
    return to_string(result); // возвращаем результат в виде строки
}

* This source code was highlighted with Source Code Highlighter.


Добавим код для main() и получим консольное приложение, которое выполняет переданный javascript код в первом параметре:

int main(int argc, char *argv[])
{
    if (argc < 2) return -1;
    try
    {
        string res = v8_exec(*++argv);
        printf("result: %s", res.data());
    }
    catch (string& err)
    {
        printf("error: %s", err.data());
    }
}

* This source code was highlighted with Source Code Highlighter.


Запустим:

XXX.exe “var s = 0; for(var i = 1; i < 100; i++) s += i; s;»

И видим:

result: 4950

Tags:
Hubs:
Total votes 47: ↑41 and ↓6 +35
Views 23K
Comments Comments 36