1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【Duktape】javascriptからC関数を呼び出す

Last updated at Posted at 2018-11-17

##概要
Duktapeライブラリを使ってC/C++言語からjavascriptの関数を呼び出す方法を書いていきます。
また、LuaのAPIとの比較もしていきます。
エラー処理は省いてます。

##javascriptから呼び出せるC/C++の関数のシグネチャ

typedef duk_ret_t (*duk_c_function)(duk_context *ctx);

javascriptから呼び出せるC言語側の関数はduk_c_function型です
戻り値のduk_ret_t型は数値型のtypedefですが、
0を返すとjavascript側ではundefinedを受け取ることになります。
1を返すとスタックの一番上にある値を受け取ることになります。
負の数はエラーを表します。

##Luaから呼び出せるC/C++の関数のシグネチャ

typedef int (*lua_CFunction) (lua_State *L);

ちなみにluaの場合はlua_CFunction 型です
luaは複数の戻り値を返却できるので返却する戻り値の数をreturnします。

##例:C側で「足し算する関数」と「表示する関数」を登録してスクリプトから呼び出す
###javascript編

jsファイルに分けると読み込み処理とかで煩雑になるので、例ではCPPソースファイルに直接書き込んでます。以下のスクリプトを実行する例です。

script.js
cout('AAA', 'BBB', 'CCC', 111);
var res=plus(10, 20);
cout('10+20=' + res.toString());
  • cout関数は引数をコンソールに出力する関数
  • plus関数は引数を足し算する関数

####期待する実行結果

引数[0]:AAA
引数[1]:BBB
引数[2]:CCC
引数[3]:111
引数[0]:10+20=30

####C++コード

coutとplus関数をCで定義していきます。

duk.cpp
#include <duktape.h>
#include <iostream>

//スクリプトから呼び出せる足し算をする関数
duk_ret_t my_plus(duk_context *ctx){

	//1番目の引数を数値型で取得
	//duk_double_tはdoubleでtypedefされています
	duk_double_t arg1= duk_get_number(ctx, 0);

	//2番目の引数を数値型で取得
	duk_double_t arg2= duk_get_number(ctx, 1);

	//引数1+引数2を足し算してスタックに積む
	duk_push_number(ctx, arg1+arg2);

	//スタックに最後に積まれた値を返却する場合は1
	return 1;
}

//スクリプトから呼び出せるコンソールに出力する関数
duk_ret_t my_cout(duk_context *ctx){

	//引数の数を取得
	duk_idx_t argc= duk_get_top(ctx);

	//引数の内容を表示する
	for(duk_idx_t i=0; i<argc; ++i){
		//文字列として引数を取得、文字列として取得できない場合は{(unknown)」を返す
		const char* str=duk_get_string_default(ctx, i, "(unknown)");
		std::cout << "引数["<< i << "]:" <<  str << std::endl;
	}

	//javascript側には返却しないので0(undefinedを返却)
	return 0;
}

int main(int argc, char** argv){

	//初期化
	duk_context *ctx = duk_create_heap_default();

	//グローバルオブジェクトをスタック[0]に積む
	duk_push_global_object(ctx);

	//【my_cout関数の登録】
	//スクリプト側に設定する関数名をスタック[1]に積む
	duk_push_string(ctx, "cout");

	//スクリプト側に設定する関数をスタック[2]に積む
	//3番目の引数は受け取る引数の数を指定しますが
	//DUK_VARARGSを指定しておくと引数が可変になります。
	duk_push_c_function(ctx, my_cout, DUK_VARARGS);

	//スタック[0]のグローバルオブジェクトに
	//Cの関数my_cout(スタック[1])を「cout」という名前(スタック[2])で登録
	//スタック[1]と[2]は削除されます。
	duk_put_prop(ctx, 0);


	//【plus関数の登録】
	//スクリプト側に設定する関数名をスタック[1]に積む
	duk_push_string(ctx, "plus");

	//スクリプト側に設定する関数をスタック[2]に積む
	//3番目の引数は受け取る引数の数を指定しますが
	duk_push_c_function(ctx, my_plus,  DUK_VARARGS);

	//スタック[0]のグローバルオブジェクトに
	//Cの関数my_plus(スタック[1])を「plus」という名前(スタック[2])で登録
	//スタック[1]と[2]は削除されます。
	duk_put_prop(ctx, 0);


	//javascriptを実行
	duk_peval_string(ctx, 
		"cout('AAA', 'BBB', 'CCC', 111);"
		"var res=plus(10, 20);"
		"cout('10+20=' + res.toString());"
	);
	
	//廃棄処理
	duk_destroy_heap(ctx);

	return 0;
}

####Cの関数を登録する手順
1.関数を(プロパティとして)登録する対象のオブジェクトをスタックに積みます。グローバルに登録するならduk_push_global_object関数。
2.関数名を文字列でスタックに積みます。(duk_push_string関数)
3.Cの関数をスタックに積みます。(duk_push_c_function関数)
4.関数を登録します。(duk_put_prop関数)

#####duk_push_global_object関数
( https://duktape.org/api.html#duk_push_global_object )
グローバルオブジェクトをスタックに積みます。

※Luaの場合→なし?
※lua_gettable(L, LUA_GLOBALSINDEX)でグローバルテーブルを取得できるかなーと思ったけどできないっぽい。そりゃそうか・・・

#####duk_push_string関数
( https://duktape.org/api.html#duk_push_string )
文字列をスタックに積みます。

数値(double型)を積む場合→「duk_push_number関数( https://duktape.org/api.html#duk_push_number )」

※Luaの場合→lua_pushstring関数・lua_pushnumber関数

#####duk_push_c_function関数
( https://duktape.org/api.html#duk_push_c_function )
Cの関数をスタックに積みます。

・3番目の引数は、C関数が呼び出されたときに受け取る引数の数を指定します。(可変ならDUK_VARARGS(-1)を指定します。)
・C関数のシグネチャは → duk_ret_t (duk_context *ctx);
・duk_push_c_function関数はECMAScriptの関数オブジェクトととして生成するのでこの関数自体に付加情報をプロパティとして追加することができます。軽量な(プロパティを設定できない)関数を追加する場合はduk_push_c_lightfunc関数を使います。

※Luaの場合→lua_pushcfunction関数

#####duk_put_prop関数
( https://duktape.org/api.html#duk_put_prop )
オブジェクトのプロパティを設定します。

  • スタックの上から1番目はプロパティの「値(value)」
  • スタックの上から2番目はプロパティの「名前(key)」

でなければなりません。
プロパティを設定する対象のオブジェクトはとりあえずスタックに積んでおけば、duk_put_prop関数の2番目の引数でスタックのインデックスを指定できます。

※Luaの場合→lua_settable関数

#####duk_get_top関数
( https://duktape.org/api.html#duk_get_top )
現在スタックに積まれている数を取得します。
C関数がスクリプトから呼び出された段階では、引数の数だけスタックが積まれているので、引数の数を取得するのにも使えます。

※Luaの場合→lua_gettop関数

#####duk_get_string_default関数
( https://duktape.org/api.html#duk_get_top )
スタックからインデックスを指定してCの文字列(const char*)を取得します。

・類似するduk_get_string関数はCの文字列(const char*)として取得できない場合(オブジェクトや数値型だった場合など)はnullptrを返しますが、duk_get_string_default関数の場合はnullptrの代わりに3番目の引数に指定した文字列を返却してくれます。
・数値(double型)として取得する場合→「duk_get_number関数( https://duktape.org/api.html#duk_get_number )」
・duk_get_number関数は数値(double型)として取得できない場合「NaN」を返します(「0」ではない)。

※Luaの場合→lua_tostring関数・lua_tonumber関数

####実行結果

引数[0]:AAA
引数[1]:BBB
引数[2]:CCC
引数[3]:(unknown)
引数[0]:10+20=30

残念ながら「111」と表示してほしいところが「(unknown)」になっています。
111は数値型なのでduk_get_string関数やduk_get_string_default関数では取得できません。
だからといって、単純にduk_get_string_defaultをduk_get_numberに書き換えれば、今度は文字列が表示されなくなります。動的に型を判別する必要があります。

####動的に型を判別して分岐する
my_cout関数を以下のように修正します。

//スクリプトから呼び出せるコンソールに出力する関数
duk_ret_t my_cout(duk_context *ctx){

	//引数の数を取得
	duk_idx_t argc = duk_get_top(ctx);

	//引数の内容を表示する
	for(duk_idx_t i=0; i<argc; ++i){

		//型を取得
		duk_int_t ty = duk_get_type(ctx, i);

		switch(ty){
			case DUK_TYPE_NUMBER:{
				//数値として引数を取得
				duk_double_t num=duk_get_number(ctx, i);
				std::cout << "引数["<< i << "]:" <<  num << std::endl;

			}break;
			case DUK_TYPE_STRING:{
				//文字列として引数を取得
				const char* str=duk_get_string(ctx, i);
				std::cout << "引数["<< i << "]:" <<  str << std::endl;
			}break;
			default:{
				//数値と文字列以外は(unknown)と表示
				std::cout << "引数["<< i << "]:" <<  "(unknown)" << std::endl;
			}break;
		}

	}

	//javascript側には返却しないので0(undefinedを返却)
	return 0;
}

#####duk_get_type関数
( https://duktape.org/api.html#duk_get_type )
スタックからインデックスを指定してその型を取得します。
型を表す値が返ってきます。
https://duktape.org/api.html#stack-type

返値
DUK_TYPE_NUMBER 数値
DUK_TYPE_STRING 文字列
DUK_TYPE_UNDEFINED undefined
DUK_TYPE_NULL null
DUK_TYPE_BOOLEAN boolean(trueかfalse)
DUK_TYPE_OBJECT Object
DUK_TYPE_BUFFER バッファー
DUK_TYPE_POINTER voidポインタ
DUK_TYPE_LIGHTFUNC (関数オブジェクトではない)C関数

※Luaの場合→lua_type関数

###Lua編

Luaで書くとこんな感じ。
詳しい説明は省略です。

script.lua
cout('AAA', 'BBB', 'CCC', 111);
local res=plus(10, 20);
cout('10+20='..tostring(res));

####C++コード

lua.cpp
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <iostream>

int my_plus(lua_State *L){

	//1番目の引数を数値型で取得
	//lua_Number型はdoubleでtypedefされています。
	//注意:Luaのスタックのインデックスは1から始まります。
	lua_Number arg1= lua_tonumber(L, 1);

	//2番目の引数を数値型で取得
	lua_Number arg2= lua_tonumber(L, 2);

	//引数1+引数2を足し算してスタックに積む
	lua_pushnumber(L, arg1+arg2);

	//戻り値の数は1つ、なので1を返却
	return 1;
}

//スクリプトから呼び出せるコンソールに出力する関数
int my_cout(lua_State *L){

	//引数の数を取得
	int argc= lua_gettop(L);

	//引数の内容を表示する
	//注意:Luaのスタックのインデックスは1から始まります。
	for(int i=1; i<=argc; ++i){

		//型を取得
		int ty=lua_type(L, i);

		switch(ty){
			case LUA_TNUMBER:{
				//数値として引数を取得
				lua_Number num=lua_tonumber(L, i);
				std::cout << "引数["<< i << "]:" <<  num << std::endl;

			}break;
			case LUA_TSTRING:{
				//文字列として引数を取得
				const char* str=lua_tostring(L, i);
				std::cout << "引数["<< i << "]:" <<  str << std::endl;
			}break;
			default:{
				//数値も文字列以外は(unknown)と表示
				std::cout << "引数["<< i << "]:" <<  "(unknown)" << std::endl;
			}break;
		}

	}

	//返却しないので0
	return 0;
}

int main(int argc, char** argv){

	//初期化
	lua_State *L = luaL_newstate();

	//tostring関数とか使うために必要
	luaL_openlibs(L);

	//Luaではグローバルを取得することはできないぽい?
	//lua_gettable(L, LUA_GLOBALSINDEX);


	//【my_cout関数の登録】
	//スクリプト側に設定する関数名をスタック[1]に積む
	//注意:Luaのスタックのインデックスは1から始まります。
	lua_pushstring(L, "cout");

	//スクリプト側に設定する関数をスタック[2]に積む
	lua_pushcfunction(L, my_cout);

	//グローバルに
	//Cの関数my_cout(スタック[1])を「cout」という名前(スタック[2])で登録
	//スタック[1]と[2]は削除されます。
	lua_settable(L, LUA_GLOBALSINDEX);


	//【plus関数の登録】
	//スクリプト側に設定する関数名をスタック[1]に積む
	lua_pushstring(L, "plus");

	//スクリプト側に設定する関数をスタック[2]に積む
	lua_pushcfunction(L, my_plus);

	//グローバルに
	//Cの関数my_plus(スタック[1])を「plus」という名前(スタック[2])で登録
	//スタック[1]と[2]は削除されます。
	lua_settable(L, LUA_GLOBALSINDEX);
	
	//luaスクリプトを実行
	luaL_dostring(L,
		"cout('AAA', 'BBB', 'CCC', 111);"
		"local res=plus(10, 20);"
		"cout('10+20='..tostring(res));"
	);

	//廃棄処理
	lua_close(L);

	return 0;
}

####実行結果
※Luaはインデックスが1から始まる

引数[1]:AAA
引数[2]:BBB
引数[3]:CCC
引数[4]:111
引数[1]:10+20=30
1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?