( ꒪⌓꒪) ゆるよろ日記

( ゚∀゚)o彡°オパーイ!オパーイ! ( ;゚皿゚)ノシΣ フィンギィィーーッ!!!

ʕ  ゚皿゚ ʔ Golangからv8を使う

cgoとlibv8を使って、タイトルのとおりのものを作ってみた。頑張ればnodeみたいなのをgoで書けるかもナー。
ʕ  ゚皿゚ ʔ cgo楽しいおシーゴォー


基本的な方法は以下の記事にあるとおりだが、v8のバージョンが上がっていたりするので、多少の手直しをしてある。

Embedding V8 Javascript Engine and Go | Brave New Method


コードはすべてGithubにある

yuroyoro/golang_v8_Embedding_sample · GitHub


まず、libv8を使う以下のようなwrapperをc++で用意して、cgoから使えるようにしておく。

v8warpper.h

#ifndef _V8WRAPPER_H
#define _V8WRAPPER_H

#ifdef __cplusplus
extern "C" {
#endif
    // compiles and executes javascript and returns the script return value as string
    char * runv8(const char *jssrc);

#ifdef __cplusplus
}
#endif

#endif // _V8WRAPPER_H


v8warpper.cc

#include <v8.h>
#include <string.h>

#include "v8wrapper.h"

using namespace v8;

char * runv8(const char *jssrc)
{
    // Get the default Isolate created at startup.
    Isolate* isolate = Isolate::GetCurrent();

    // Create a stack-allocated handle scope.
    HandleScope handle_scope(isolate);

    // Create a new context.
    Handle<Context> context = Context::New(isolate);

    // Enter the context for compiling and running the hello world script.
    Context::Scope context_scope(context);

    // Create a string containing the JavaScript source code.
    Handle<String> source = String::New(jssrc);

    // Compile the source code.
    Handle<Script> script = Script::Compile(source);

    // Run the script to get the result.
    Handle<Value> result = script->Run();

    // The JSON.stringify function object
    Handle<Object> global = context->Global();
    Handle<Object> JSON = global->Get(String::New("JSON"))->ToObject();
    Handle<Function> JSON_stringify = Handle<Function>::Cast(JSON->Get(String::New("stringify")));

    Handle<Value> args[] = { result };
    // stringify result
    Local<Value> json = JSON_stringify->Call(JSON, 1, args);

    // Convert the result to an UTF8 string and print it.
    String::Utf8Value utf8(json);

    // return result as string, must be deallocated in cgo wrapper
    return strdup(*utf8);
}


runv8(char)は、引数の文字列のjavascriptをv8で実行し、結果の値をJSON.stringifyした結果の文字列を返す。
なお、サンプルなのでエラー処理はしていない( ;゚皿゚)。

で、こいつを適当にmakeする。以下のmakefileosx

Makefile.warpper

V8_INC=/usr/local/Cellar/v8/3.19.18.4/include
V8_LIBDIR=/usr/local/Cellar/v8/3.19.18.4/lib/libv8.dylib

CC=g++
CFLAGS= -I$(V8_INC) -I/usr/include -lv8 -dynamiclib -o $(TARGET)
SOURCES=v8wrapper.cc
OBJECTS=$(SOURCES:.cc=.o) $(V8_DYLIB)
TARGET=libv8wrapper.dylib

all: $(TARGET)


$(TARGET): $(OBJECTS)
	$(CC) $(CFLAGS) $< -o $@

clean:
	rm $(TARGET) $(OBJECTS)


あとは、cgoでこのv8warpperをlinkして使えばいい。


v8runner.go

package main

// #cgo LDFLAGS: -L. -lv8wrapper -lv8  -lstdc++
// #include <stdlib.h>
// #include "v8wrapper.h"
import "C"
import (
	"encoding/json"
	"fmt"
	"unsafe"
)

func RunV8(script string, result interface{}) error {

	// convert Go string to nul terminated C-string
	cstr := C.CString(script)
	defer C.free(unsafe.Pointer(cstr))

	// run script and convert returned C-string to Go string
	rcstr := C.runv8(cstr)
	defer C.free(unsafe.Pointer(rcstr))

	jsonstr := C.GoString(rcstr)

	fmt.Printf("Runv8 json -> %s\n", jsonstr)
	// unmarshal result
	err := json.Unmarshal([]byte(jsonstr), result)
	if err != nil {
		return err
	}

	fmt.Printf("Runv8 Result -> %T: %#+v\n", result, result)
	return nil
}


RunV8では、'encoding/json'を利用して、runv8の戻り値である文字列のJSONを、goの値にunmarshalする。
以下のように文字列でscriptをRunV8関数に渡せばinterface型で結果を取得できる。

package main

import (
	"fmt"
)
func main() {

	scripts := []string{
		"null",
		"true",
		"123",
		"457.78",
		"[10, 20, 30]",
		"'Hello, World'",
		"new Date()",
		`obj = {"foo": [1, 2], "bar": {"baz": true, "hoge": "fuga"}}`,
	}

	for _, s := range scripts {
		fmt.Printf("Script -> %s\n", s)

		var res interface{}
		RunV8(s, &res)
		fmt.Printf("Result -> %T: %#+v\n\n", res, res)
	}
}

実行結果

Script -> null
Result -> <nil>: <nil>

Script -> true
Result -> bool: true

Script -> 123
Result -> float64: 123

Script -> 457.78
Result -> float64: 457.78

Script -> [10, 20, 30]
Result -> []interface {}: []interface {}{10, 20, 30}

Script -> 'Hello, World'
Result -> string: "Hello, World"

Script -> new Date()
Result -> string: "2014-06-11T08:58:43.951Z"

Script -> obj = {"foo": [1, 2], "bar": {"baz": true, "hoge": "fuga"}}
Result -> map[string]interface {}: map[string]interface {}{"foo":[]interface {}{1, 2}, "bar":map[string]interface {}{"baz":true, "hoge":"fuga"}}


このように、JSONをstructにmappingさせることもできる。

package main

import (
	"fmt"
)

type Foo struct {
	Foo []int `json:"foo"`
	Bar Bar   `json:"bar"`
}

type Bar struct {
	Baz  bool   `json:"baz"`
	Hoge string `json:"hoge"`
}

func main() {

	script := `obj = {"foo": [1, 2], "bar": {"baz": true, "hoge": "fuga"}}`
	var result Foo

	fmt.Printf("Script -> %s\n", script)
	RunV8(string(script), &result)
	fmt.Printf("Result -> %T: %#+v\n", result, result)

}

実行結果

Script -> obj = {"foo": [1, 2], "bar": {"baz": true, "hoge": "fuga"}}
Result -> main.Foo: main.Foo{Foo:[]int{1, 2}, Bar:main.Bar{Baz:true, Hoge:"fuga"}}