2015年11月1日日曜日

Google V8 搭載 (2015年度版)

メイン PC を Win10 にするついでに,開発ツールを Visual Studio Community 2015 (Express ではない,ほぼフルスペックの VS がタダで使える.いい時代だなぁ(゜ーÅ)ほろり) を入れたら,3年前の Google V8 ソースコードがコンパイルできなかったので,最新版を VSD for GPS に組み込むことにした.
こんだけ期間が開くと API がガラッと変わって,昔やってた AviUtl のプラグイン DLL に V8 を組み込む手法なんかも変えないといけなかったので,再び調べてみた.

サンプルとかでよく見る V8 の実行の流れは以下のようになっていて,
あるクラス::ある関数(){
  Isolate 作成
  Isolate スコープの中 {
    Handle スコープの中 {
      Context 作成
      Cotext スコープの中 {
        JavaScript 初期化 (スクリプトコンパイル等)
        // ↑ここまで初期化処理
        // ↓ここから繰り返しの処理
        JavaScript 実行
      }
    }
  }
}
AviUtl のプラグイン DLL では,初期化処理と JavaScript 実行処理は関数を分けて,それぞれの関数処理が終わるごとに AviUtl に制御を返さないといけない.かと言って単純にそれらを分けてしまうと,初期化関数を抜けた時に Handle スコープを抜ける = Context も破棄されるため,別関数で JavaScript 実行することができない.

なので以下のようにする.
あるクラス::Compile(){
  Isolate 作成→メンバ変数に保存
  Isolate スコープの中 {
    Handle スコープの中 {
      Context 作成→メンバ変数に保存
      Cotext スコープの中 {
        JavaScript 初期化 (スクリプトコンパイル等)
      }
    }
  }
}

あるクラス::Run(){
  メンバ変数に保存した Isolate スコープの中 {
    Handle スコープの中 {
      メンバ変数に保存した Cotext スコープの中 {
        JavaScript 実行
      }
    }
  }
}
要は Isolate と Context はメンバ変数に保存してやれば良い.ただし Context::New は Local<> なので (そのままでは Handle スコープを抜けると破棄されるため) Persistent<> に変換してやる必要がある.(そして Run() 内でメンバ変数の Context を参照するときは,また Local<> に変換する.)
Context 内のグローバルオブジェクトとか,HandleScope を抜けるとどうなるか心配だったが,この方法なら破棄されないみたい.

というわけで hello.cc をちょっと改造して上記のことを実現したソースは以下のとおり.Compile メソッドで生成した JavaScript function の hoge() を,Run メソッドで呼ぶことができている.昔やってた,HandleScope を new で生成するというやばい方法は使わなくて済んだ.
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

using namespace v8;

class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
public:
    virtual void* Allocate(size_t length) {
        void* data = AllocateUninitialized(length);
        return data == NULL ? data : memset(data, 0, length);
    }
    virtual void* AllocateUninitialized(size_t length) { return malloc(length); }
    virtual void Free(void* data, size_t) { free(data); }
};

class CScript {
    
    Platform* m_Platform;
    ArrayBufferAllocator m_Allocator;
    Isolate::CreateParams m_CreateParams;
    Isolate* m_Isolate;
    v8::Persistent<v8::Context> *m_Context;
    
  public:
    CScript( char *path ){
        // Initialize V8.
        V8::InitializeICU();
        V8::InitializeExternalStartupData( "." );
        m_Platform = platform::CreateDefaultPlatform();
        V8::InitializePlatform(m_Platform);
        V8::Initialize();
        m_CreateParams.array_buffer_allocator = &m_Allocator;
    }
    
    ~CScript(){
        m_Isolate->Dispose();
        V8::Dispose();
        V8::ShutdownPlatform();
        delete m_Platform;
    }
    
    void Compile( void ){
        // Create a new Isolate and make it the current one.
        m_Isolate = Isolate::New(m_CreateParams);
        {
            Isolate::Scope isolate_scope(m_Isolate);
            
            // Create a stack-allocated handle scope.
            HandleScope handle_scope(m_Isolate);
            
            // Create a new context.
            Local<Context> context = Context::New(m_Isolate);
            m_Context = new Persistent<v8::Context>( m_Isolate, context );
            
            // Enter the context for compiling and running the hello world script.
            Context::Scope context_scope(context);
            
            // Create a string containing the JavaScript source code.
            Local<String> source =
                String::NewFromUtf8(m_Isolate, "function hoge(){ return 'hoge!!!'; }",
                    NewStringType::kNormal).ToLocalChecked();
            
            // Compile the source code.
            Local<Script> script = Script::Compile(context, source).ToLocalChecked();
            Local<Value> result = script->Run(context).ToLocalChecked();
        }
    }
    
    void Run( void ){
        Isolate::Scope isolate_scope(m_Isolate);
        
        // Create a stack-allocated handle scope.
        HandleScope handle_scope(m_Isolate);
        
        // Create a new context.
        Local<Context> context = Local<Context>::New( m_Isolate, *m_Context);
        
        // Enter the context for compiling and running the hello world script.
        Context::Scope context_scope(context);
        
        Local<Function> hFunction = Local<Function>::Cast( context->Global()->Get( String::NewFromUtf8( m_Isolate, "hoge" )));
        if( hFunction->IsUndefined()){
            int a=0;
        }
        
        Local<Value> result = hFunction->Call( context->Global(), 0, NULL);
        // Run the script to get the result.
        
        // Convert the result to an UTF8 string and print it.
        String::Utf8Value utf8(result);
        printf("%s\n", *utf8);
    }
};

int main(int argc, char* argv[]) {
    
    CScript *scr = new CScript( argv[ 0 ]);
    scr->Compile();
    scr->Run();
    delete scr;
    
    return 0;
}

0 件のコメント:

コメントを投稿