凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

C++ から JavaScript 側へ簡単に関数をエクスポートして実行するクラスを作ってみた

はじめに

V8 と戯れてました。…もとい虐められてました。V8 流行ってから相当経つし、ネット上にかなりの情報が転がってるだろう、とタカをくくって取り組み始めたのですが、やりたかったことを実現しようとすると、うーむ、となってしまい結構悩みました(ちなみに未だ解決してないです)。取りあえず理解してないなりにスクリプトを実行する部分は出来たので載せたいと思います。

参考文献+お勉強に使わせてもらった場所

他にも色々見ましたが忘れてしまいました…。

コード

v8.hpp
#ifndef INCLUDE_V8_HPP
#define INCLUDE_V8_HPP

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

class V8
{
public:
	V8();
	~V8();
	void create_context();

	template <class T>
	void set(const std::string& func_name, T func)
	{
		global_->Set(
			v8::String::New(func_name.c_str()),
			v8::FunctionTemplate::New(func)
		);
	}

	bool exec_script(const std::string& file_name);

private:
	v8::HandleScope handle_scope_;
	v8::Persistent<v8::ObjectTemplate> global_;
	v8::Persistent<v8::Context> context_;
};

#endif // INCLUDE_V8_HPP
v8.cpp
#include <iostream>
#include <fstream>
#include <boost/bind.hpp>
#include "v8.hpp"

V8::V8() : global_(v8::ObjectTemplate::New())
{
}

V8::~V8()
{
	global_.Dispose();
	context_.Dispose();
}

void V8::create_context()
{
	context_ = v8::Context::New(NULL, global_);
}

bool V8::exec_script(const std::string& file_name)
{
	v8::TryCatch try_catch;

	// read js file
	std::ifstream ifs(file_name);
	if (ifs.fail()) {
		std::cerr << "js file open error: " << file_name << std::endl;
		return false;
	}
	std::string buf, source = "";
	while (std::getline(ifs, buf)) {
		source += buf.c_str();
	}
	v8::Handle<v8::String> script = v8::String::New(source.c_str());

	// compile js file
	v8::Context::Scope context_scope(context_);
	v8::Handle<v8::Script> compiled_script = v8::Script::Compile(script);
	if (compiled_script.IsEmpty()) {
		v8::String::Utf8Value error(try_catch.Exception());
		std::cerr << *error << std::endl;
		return false;
	}

	// run js file
	v8::Handle<v8::Value> result = compiled_script->Run();
	if (result.IsEmpty()) {
		v8::String::Utf8Value error(try_catch.Exception());
		std::cerr << *error << std::endl;
		return false;
	}

	return true;
}

int main(int argc, char const* argv[])
{
	V8 js;
	js.set("print", [](const v8::Arguments& args)->v8::Handle<v8::Value> {
		v8::String::Utf8Value str(args[0]);
		std::cout << *str << std::endl;
		return v8::Undefined();
	});
	js.create_context();
	js.exec_script("test.js");

	return 0;
}

(2012/04/16: ifstream の引数の間違いを修正「std::ifstream ifs("test.js");」 --> 「std::ifstream ifs(file_name);)」)

test.js
print((function(a,b){
	return a*b;
})(123,456));
コンパイルと実行
$ g++-4.6 -std=c++0x v8.cpp -I./v8/include -L./v8 -lv8 -lpthread
$ ./a.out
56088

動きました。Lambda も使えます。
コンパイルオプションは v8 をコンパイルしたディレクトリの置き場所に応じて変えて下さい。

課題

Context::New() 後の関数のエクスポート

ホントは create_context なんて明示的にやりたくなかったのですが…。Context::New() した後に set したかったのですが、どうやっていいのか分からず、仕方なく create_context メンバ関数を追加しました。
同じように悩んでる人は居るようです。

教えてエロイ人

クラスのエクスポート

クラスを簡単にエクスポートするにはどうしたら良いかなぁ、と悩んでいます。これは次回の課題にします。

終わりに

全然関係ないですが、はてダのデザインをちょっと弄りました。結構制約もありますが、やろうと思えば工夫してかなり見栄えを変えられそうですね。