LoginSignup
2
1

More than 3 years have passed since last update.

V8: ビルドと C++ への組み込み (作業メモ)

Last updated at Posted at 2020-08-10

この記事について

以下のチュートリアル・サンプルを見て JavaScript の V8 エンジンを C++ に組み込んでみた際の作業メモ(初歩的な内容。C++は詳しくないのでコードの書き方が変かも)。

使用した環境

  • Ubuntu 18.04
  • Python2.7
    • ubuntu 18.04 ではデフォルトで入らないのでインストールする
sudo apt install python2.7 python-pip
  • 開発ツール
    • build-essential をインストール
$ sudo apt install build-essential
$ g++ --version
g++ (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0

作業後に出来るディレクトリ構成

/home/kitauji/repos/
├── testapp
│   ├── main.cc
│   ├── sample.js
│   └── testapp
└── v8
    └── v8
        ├── include
        ├── samples
        ├── src
        ├── などなど多数

手順

V8用の開発環境構築

V8 のソースコードを取得

まず、depot_tools をインストールする。depot_tools は Chromium の開発で用いられてる開発ツール群。

$ cd ~/bin
$ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git

# 上記 depot_tools ディレクトリへの PATH を追加する。その後 .profile を再読み込みする
# (例) PATH="$HOME/bin/depot_tools:$PATH"
$ vi ~/.profile

# depot_tools の更新
$ gclient

※ なお、gclient はこんな感じの出力になったが、これでいいのか分からない。

WARNING: Your metrics.cfg file was invalid or nonexistent. A new one will be created.
Usage: gclient.py <command> [options]
Meta checkout dependency manager for Git.
...

fetch で V8 のソースを取得する。

$ cd ~
$ mkdir -p repos/v8
$ cd repos/v8/
$ fetch v8
$ cd v8

依存ライブラリ等をインストールする。

# Download all the build dependencies
$ cd ~/repos/v8/v8
$ gclient sync

# Install additional build dependencies
$ ./build/install-build-deps.sh

V8をスタティックライブラリとしてビルドする

今回は V8 を自前の C++ アプリに組み込みたいので、V8 をスタティックライブラリとしてビルドする。

# ビルド
$ cd ~/repos/v8/v8
$ tools/dev/v8gen.py x64.release.sample
$ ninja -C out.gn/x64.release.sample v8_monolith

# 生成されたライブラリ(libv8_monolith.a)を確認
$ file out.gn/x64.release.sample/obj/libv8_monolith.a
out.gn/x64.release.sample/obj/libv8_monolith.a: current ar archive

アプリへの V8 の組み込み

基本概念

最初に基本的な概念が分からないとコードを見てもさっぱりなので、以下に記載(https://v8.dev/docs/embed より引用):

  • An isolate is a VM instance with its own heap.
  • A local handle is a pointer to an object. All V8 objects are accessed using handles. They are necessary because of the way the V8 garbage collector works.
  • A handle scope can be thought of as a container for any number of handles. When you've finished with your handles, instead of deleting each one individually you can simply delete their scope.
  • A context is an execution environment that allows separate, unrelated, JavaScript code to run in a single instance of V8. You must explicitly specify the context in which you want any JavaScript code to be run.

自前アプリのコード記述

以下は V8のサンプルの hello-world.cc にちょっと手を入れただけ。引数で渡された JavaScript ファイルを実行するもの。

cd ~/repos
mkdir testapp
cd testapp/
vi main.cc

以下、main.cc の内容。

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>

#include "include/libplatform/libplatform.h"
#include "include/v8.h"

void Usage() {
    std::cout << "testapp <JavaScript FilePath>" << std::endl;
}

void ReadFile(const char* filePath, std::string& contents) {
    std::ifstream f(filePath);
    std::stringstream buffer;
    buffer << f.rdbuf();
    contents = buffer.str();
}

int main(int argc, char* argv[]) {
    if ( argc < 2 ) {
        Usage();
        return 1;
    }

    // Read a JavaScript file contents.
    char* filePath = argv[1];
    std::string fileContents;
    ReadFile(filePath, fileContents);

    // Initialize V8.
    v8::V8::InitializeICUDefaultLocation(argv[0]);
    v8::V8::InitializeExternalStartupData(argv[0]);
    std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
    v8::V8::InitializePlatform(platform.get());
    v8::V8::Initialize();

    // Create a new Isolate
    v8::Isolate::CreateParams isolateCreateParam;
    isolateCreateParam.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
    v8::Isolate* isolate = v8::Isolate::New(isolateCreateParam);

    {
        v8::Isolate::Scope isolateScope(isolate);
        // Create a stack-allocated handle scope.
        v8::HandleScope handleScope(isolate);
        // Create a new context.
        v8::Local<v8::Context> context = v8::Context::New(isolate);
        // Enter the context for compiling and running the hello world script.
        v8::Context::Scope contextScope(context);

        {
            // Create a string containing the JavaScript source code.
            v8::Local<v8::String> source = v8::String::NewFromUtf8(isolate, fileContents.c_str(), v8::NewStringType::kNormal).ToLocalChecked();
            // Compile the source code.
            v8::Local<v8::Script> script = v8::Script::Compile(context, source).ToLocalChecked();
            // Run the script to get the result.
            v8::Local<v8::Value> result = script->Run(context).ToLocalChecked();
            v8::String::Utf8Value utf8(isolate, result);

            std::cout << *utf8 << std::endl;
        }
    }
    // Dispose the isolate and tear down V8.
    isolate->Dispose();
    v8::V8::Dispose();
    v8::V8::ShutdownPlatform();
    delete isolateCreateParam.array_buffer_allocator;
    return 0;
}

ビルド

以下のような Makefile を作成。


INCLUDE = -I../v8/v8 -I../v8/v8/include
CXX = g++
CFLAGS = -pthread -std=c++0x -DV8_COMPRESS_POINTERS
LDFLAGS = -L../v8/v8/out.gn/x64.release.sample/obj/
LIBS = -lv8_monolith

build:
    $(CXX) $(INCLUDE) main.cc -o hello $(LDFLAGS) $(LIBS) $(CFLAGS)

そしてビルドを実行。同じディレクトリに hello という実行ファイルができる。

make build

実行

以下の JavaScript ファイルを用意(sample.js とする)。

function test() {
    return "Hello!"
}

test()

コンパイルした自前アプリを実行してみる。

$ ./testapp sample.js 
Hello!

v8 の samples ディレクトリには hello-world の他にもサンプルがあるので見てみるとよさそう。

補足

V8 で使われる各種の型、メソッド

以下、v8.h から引用。

v8::Local

template <class T>
class Local

An object reference managed by the v8 garbage collector.

All objects returned from v8 have to be tracked by the garbage collector so that it knows that the objects are still alive. Also, because the garbage collector may move objects, it is unsafe to point directly to an object. Instead, all objects are stored in handles which are known by the garbage collector and updated whenever an object moves. Handles should always be passed by value (except in cases like out-parameters) and they should never be allocated on the heap.

v8::MaybeLocal

template<class T>
class v8::MaybeLocal

A MaybeLocal<> is a wrapper around Local<> that enforces a check whether the Local<> is empty before it can be used.

  • v8::Local<T> ToLocalChecked()
    Converts this MaybeLocal<> to a Local<>. If this MaybeLocal<> is empty, V8 will crash the process.
  • template <class S> bool ToLocal(Local<S>* out) const
    Converts this MaybeLocal<> to a Local<>. If this MaybeLocal<> is empty, |false| is returned and |out| is left untouched.
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1