凹みTips

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

node.js で JavaScript 側で定義した関数を C++ から呼ぶ方法

はじめに

例えば、なんか重い作業が回っていて、それが終わったら JavaScript 側で予めセットしておいた関数を呼びたい!みたいなときに使えます。

コード

ここでは関数の名前を引数にとって呼べる call 関数を作ってみます。

main.cpp
#include <iostream>
#include <node.h>

using namespace v8;
Persistent<Object> module_target;

Handle<Value> call(const Arguments& args)
{
	HandleScope handle_scope;
	Local<Function> callback = Local<Function>::Cast(
		module_target->Get(args[0])
	);

	if (!callback->IsFunction()) {
	String::Utf8Value str(args[0]);
		std::cout << "Error! '" << *str <<  "' is not Function." << std::endl;
		return Undefined();
	}

	callback->Call(module_target, 0, nullptr);
}

void init(Handle<Object> target)
{
	module_target = Persistent<Object>::New(target);
	target->Set( String::New("call"), FunctionTemplate::New(&call)->GetFunction() );
}

NODE_MODULE(call, init)

target をグローバル化してそれを登録する関数内で使用しています。lambda でも OK です。ただ、キャプチャは v8 に怒られるので出来ません。ざんねん。

#include <iostream>
#include <thread>
#include <node.h>

using namespace v8;
Persistent<Object> module_target;

void init(Handle<Object> target)
{
	module_target = Persistent<Object>::New(target);
	target->Set(
		String::New("call"),
		FunctionTemplate::New([](const Arguments& args)->Handle<Value> {
			HandleScope handle_scope;
			Local<Function> callback = Local<Function>::Cast(
				module_target->Get(args[0])
			);

			if (!callback->IsFunction()) {
				String::Utf8Value str(args[0]);
				std::cout << "Error! '" << *str <<  "' is not Function." << std::endl;
				return Undefined();
			}

			callback->Call(module_target, 0, nullptr);
		})->GetFunction()
	);
}

NODE_MODULE(call, init)
wscript
srcdir = '.'
blddir = 'build'
VERSION = '0.0.1'

def set_options(opt):
  opt.tool_options('compiler_cxx')

def configure(conf):
  conf.check_tool('compiler_cxx')
  conf.check_tool('node_addon')
  conf.env['CXX']       = 'g++-4.8'
  conf.env['CXXFLAGS']  = '-std=c++0x'

def build(bld):
  obj = bld.new_task_gen('cxx', 'shlib', 'node_addon')
  obj.target = 'call'
  obj.source = 'main.cpp'
test.js
var addon = require('./build/Release/call');

addon.callback = function() {
	console.log("hogehoge");
};

addon.call('callback');

require して貰ったオブジェクトに callback を追加します。最初、callback をグローバルに置いてしまっていて悩みました。

コンパイル & 実行
$ node-waf configure build
$ (ずらーっとコンパイル結果)
$ node test
$ hogehoge

出来ました!ちなみに「はじめに」で言ったような重い処理のイメージは下記のようなコードになります。

#include <iostream>
#include <thread>
#include <node.h>

using namespace v8;
Persistent<Object> module_target;

Handle<Value> heavy(const Arguments& args)
{
	// 重い処理をする
	std::thread t([]{
		std::this_thread::sleep_for(std::chrono::seconds(5));
	});
	t.join();

	HandleScope handle_scope;
	Local<Function> callback = Local<Function>::Cast(
		module_target->Get( String::New("callback") )
	);

	if (!callback->IsFunction()) {
		std::cout << "Error! 'callback' is not declared." << std::endl;
		return Undefined();
	}

	callback->Call(module_target, 0, nullptr);
}

void init(Handle<Object> target)
{
	module_target = Persistent<Object>::New(target);
	target->Set( String::New("heavy"), FunctionTemplate::New(&heavy)->GetFunction() );
}

NODE_MODULE(heavy, init)

環境

  • Ubuntu 10.04
  • gcc バージョン 4.8.0 20120527 (experimental) (GCC)