関連投稿
Node.jsのモジュールをC++で作成する
Node.js + nobleを使ってMicrobitのセンサー情報を読み取る
前回では、関数をコールすると文字列が返ってくる単純なものだったので、もう少し実用的なサンプルを備忘録として残しておく。
基本は、以下を参照だが、正直多種多様なモジュールを作る気がないなら、引数付きの関数実行を覚えておけばよいと思う。
Node.js C++ Addons
あと、一番やりたかった非同期のコールバック関数の作り方は乗ってなかった(コールバック形式の作成方法はあったが、そのまま実行しても同期処理になる)ので、以下の素晴らしいサイトを参考にした。
libuvを用いた非同期処理を含むNode.js Native Addon入門
引数付きの同期処理を行う関数サンプル
作成すると、以下のような呼び出しができる
// add.js
const addon = require('./build/Release/addon');
var val = addon.add(3,5);
console.log(val);
var val2 = addon.add(1,0.033);
console.log(val2);
> node add.js
8
1.003
といっても前回のHello関数とほぼ一緒で、args
から代入された引数が取得できますよ。という違いだけになる。
// addon.cc
// addon.cc
#include <node.h>
namespace demo {
using v8::Exception;
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;
using v8::Number;
void Add( const FunctionCallbackInfo<Value> & args ){
Isolate * isolate = args.GetIsolate();
// check argument
if( args.Length() < 2 ){
// argument error
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate, "Wrong number of arguments")
));
return;
}
if( !args[0]->IsNumber() || !args[1]->IsNumber() ){
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate, "Wrong arguments")
));
return;
}
// add operation
double value = args[0]->NumberValue() + args[1]->NumberValue();
// set return value
Local<Number> num = Number::New(isolate, value);
args.GetReturnValue().Set(num);
}
void Init(Local<Object> exports) {
NODE_SET_METHOD(exports, "add", Add);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, Init)
}
binding.gypファイル
{
"targets": [
{
"target_name": "addon",
"sources": [ "addon.cc" ],
"conditions":[
[
'OS=="linux"',{
"defines": [ "_GNU_SOURCE" ],
"cflags": [ "-g", "-O2", "-std=c++11", ]
}
],
[
'OS=="win"',{
'libraries': [
'dbghelp.lib',
'Netapi32.lib'
],
'dll_files': [
'dbghelp.dll',
'Netapi32.dll'
]
}
],
[
'OS=="mac"',{
'xcode_settings': {
'CLANG_CXX_LIBRARY': 'libc++',
'CLANG_CXX_LANGUAGE_STANDARD':'c++11',
}
}
]
]
}
]
}
非同期のコールバック関数(ノンブロッキング?)
以下のようなjavascriptを記述して非同期処理できる関数が作成できる
const addon = require('./build/Release/addon');
console.log("-- start --");
addon.async( 3, 5, function(result){
console.log("async = "+result);
});
console.log("-- end --");
endのあとに結果が表示されているため、ノンブロッキング処理になっていることがわかる。
> node async.js
-- start --
-- end --
async = 45
ポイントは、libuv
というライブラリを使用するところで、linuxのイベントループ処理をおこなうlivevent
やlibev
をマルチプラットフォーム化したライブラリで、Windowsでもビルドできるモジュールらしい。
以下のサンプルでは、AsyncData
とクラスでスレッドデータをやり取りして、非同期で実行したい負荷の高い処理をCompute
で処理する。ほかは、ほぼお約束?のようなコードだと思っている。
いずれの関数名も制約はないので、好きな名前でOK。
// addon.cc
#include <node.h>
#include <iostream>
#include <string>
#include <uv.h>
#include <unistd.h>
namespace demo {
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Number;
using v8::Value;
using v8::Exception;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::HandleScope;
using v8::Persistent;
// スレッド間でやり取りするデータ
class AsyncData {
public:
uv_work_t uv_request;
Persistent<Function> callback;
int number;
int number2;
std::string result;
};
// 非同期実行したい重たい処理
std::string Compute(int number, int number2) {
int i;
long y = 0;
for(int i = number2; i > 0; i--){
y += i * number;
}
return std::to_string(y);
}
// スレッド処理
static void AsyncWork(uv_work_t *req) {
//Threadデータからデータを取り出して計算実行
AsyncData *work = static_cast<AsyncData *>(req->data);
std::string result = Compute(work->number, work->number2);
// 結果を設定する
work->result = result;
}
// スレッド完了時の処理
static void AsyncAfter(uv_work_t *req, int status) {
Isolate *isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
// スレッドデータから計算結果を取り出してコールバックの引数に詰める
AsyncData *work = static_cast<AsyncData *>(req->data);
Local<String> result = String::NewFromUtf8(isolate, work->result.c_str());
const unsigned argc = 1;
Local<Value> argv[argc] = {result};
// コールバックの呼び出し
Local<Function> cb = Local<Function>::New(isolate, work->callback);
cb->Call(isolate->GetCurrentContext()->Global(), argc, argv);
// 不要になったスレッドを削除
work->callback.Reset();
delete work;
}
// 非同期処理の開始
void Async(const FunctionCallbackInfo<Value> &args) {
Isolate *isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
// 引数取得
int number = args[0]->NumberValue();
int number2 = args[1]->NumberValue();
Local<Function> cb_local = Local<Function>::Cast(args[2]);
// スレッドデータ作成
AsyncData *work = new AsyncData;
work->uv_request.data = work;
work->callback.Reset(isolate, cb_local);
work->number = number;
work->number2 = number2;
// イベントループ作成
uv_loop_t *loop = uv_default_loop();
// スレッド開始
uv_queue_work(
loop,
&work->uv_request,
(uv_work_cb)AsyncWork,
(uv_after_work_cb)AsyncAfter);
}
void Init(Local<Object> exports, Local<Object> module) {
//NODE_SET_METHOD(module, "exports", Async);
NODE_SET_METHOD(exports, "async", Async);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, Init);
}