Разработка

индекс
203,40

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

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

+35
16 октября 2009, 13:30
37

комментарии (30)

–10
aktuba #
Может я чего-то недопонял, но… Какой смысл ставить v8 отдельно от браузера?
+13
homm #
Ты чего-то не понял. Точно.
+1
aktuba #
Упс… Как-то пропустил «качестве серверного скриптового движка»… Сорри.
0
homm #
Он может использоваться в любой программе. автокады, матлабы, все они используют какие-то скриптовые языки.
+5
RommeDeSerieux #
Смысл в том, что javascript является языком общего назначения и при наличии нужного API можно использовать его для чего угодно.
0
homm #
А что считается результатом выполнения скрипта? Т.е. я вижу, что это «s;», Получается, последнее выполненное выражение?
0
Krovosos #
в данном примере — да
0
overPlumbum #
А у него «из-коробки» есть что-нибудь консольное для запуска js, по аналогии с коммандой «js», как у SpiderMonkey?
так очень удобно js-unit-тесты и JsLint на сервере гонять. Можно было бы сразу на двух движках тестировать…

0
Krovosos #
У него идет пример в проекте v8_shell_sample.vcproj
Это консольное приложение, исходный код в samples\shell.cc
Подробностей не знаю, но вроде бы позволяет запускать код js.

Также есть d8. Это фактически shell с отладчиком, но увы — почти не документирован.

www.mail-archive.com/v8-users@googlegroups.com/msg01465.html
+3
boh #
Вы бы до ката коротенько пояснили, что за v8. Не все настолько глубоко интересуются внутренностями браузеров.
0
Krovosos #
Пояснил!
–2
jjlol #
Используйте tracemonkey (spidermonkey). Хотя у него и сишный api, но он более удобный. Меня, к тому же сильно раздражает в гугловском движке то, что он активно использует стек для GC и прочих вещей. (В коде это хорошо видно по try_catch). Хотя, конечно, он быстрый =)
+2
egorinsk #
> что он активно использует стек для GC и прочих вещей

а что в этм плохого?
0
vectoroc #
А что это в итоге будет? для какого сервиса V8 «в качестве серверного скриптового движка».?
0
Krovosos #
Собственная платформа, код бизнес-логики написан на javascript
0
developer #
круто! давно искал на чем бы писать скрипты для ботов в игре! JS подходит идеально! а как воткнуть эту штуку в Java где написано?
0
gnomeby #
Посмотрите в сторону Lua, как раз используется для таких целей очень часто.
+1
nayjest #
JS намного мощнее и удобней LUA, писать скрипты для игр нем было бы действительно замечательно!
+1
gnomeby #
Спорный вопрос, ибо:
  • Пока Lua уделывает популярные Javascript движки. Это конечно не V8, но пока цифры отличаются на такой порядок, что V8 сильно картину не улучшит.
  • Lua изначально спроектирован для встраеваемости
  • Lua имеет реализации для большого числа языков программирования
  • Lua не плодит сущности сверх необходимого в отличии от Javascript
  • Lua выбран флагманами игровой индустрии
  • Javascript — плохой язык программирования. Он допилен, доточен отдельными производителями, чтобы присыпать отдельные проблемы, но всё-равно их остаётся много, просто он уже стал стандартом дефакто и от него никуда не уйти
0
Krovosos #
А почему вот эту ссылочку не привели:

shootout.alioth.debian.org/u32q/benchmark.php?test=all&lang=v8&lang2=lua&box=1

?

Уделал V8 ваш любимый Lua кое-где. Так что «Это конечно не V8, но пока цифры отличаются на такой порядок, что V8 сильно картину не улучшит.» это всего лишь эротические фантазии, к реальности не относятся… ;-)
0
mraleph #
справедливости ради: сравнивать надо не с lua, а с luajit 2, который побыстрее будет на shootout. (кроме бенчмарка, который на gc давит).

но да ему сам бог велит — семантика у lua простая и изящная, компилировать и оптимизировать одно удовольствие.
0
mraleph #
во блин я некромансер *facepalm* на дату и не посмотрел…
0
tangro #
Да, комментировать посты годичной давности смысла нету.
0
Krovosos #
Кроме того, Javascript — замечательный язык программирования!
0
joedm #
Это не подойдёт? www.mozilla.org/rhino/
Конечно, не v8, но может и сгодится.
+1
EjikVTumane #
Удобней всего для Java использовать скриптовые языки которые изначально поддерживают Java scripting API. Например Groovy и BeanShell. Мы использовали вполне успешно и тот и другой в проектах. Последнее время предпочтение отдается Groovy. На офсайтах обоих проектов есть довольно подробное описание с примерами использования.
0
Methos #
Быстрее php будет?
0
std #
а сколько весит бинарник Вашего тестового консольного приложения?
0
nicity #
Спасибо за статью, script с большой буквы в коде функции v8_exec

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.