その5 LuaからC言語の関数を呼び出す
C言語側からLuaの関数を呼び出すのはその2「Luaスクリプト事始め」で説明しました。Luaステートがうまいこと仲立ちしてくれているためとっても簡単です。では、Luaスクリプト側からC言語の関数を呼び出すにはどうしたらよいか?この章ではその辺りを掘り下げていきます。Luaを扱う上でとっても重要な部分です。
@ C言語側でLuaステートに関数を教えるのが第1歩
例えば、次のようなLuaスクリプトを書いたとします:
その関数は? start = 10.0f;
goul = 30.0f;
--補間しよう
t = 0.0;
while t <= 1.0 do
print(Linear(start, end, t));
t = t + 0.1;
end
startとendの値の補間値を取得するLinear関数を使っています。でも、この関数はLuaのライブラリにはありません。無いのに使おうとしている。当然ですが、このままだとエラーとなります。
でも、実はこのLuaスクリプトを呼び出してもエラーにならなくする方法があります。それは、このスクリプトを呼び出す前にLuaステートに「Linear関数を登録」してあげればいいんです。
登録する方法としては前章で出てきた「require関数」を使ってLinear関数が定義されているLuaファイルを指定するのが一つあります:
MyLib.lua --線形補間
function Linear(star, end, t)
return (1 - t) * start + t * end;
end
require関数で呼び出し require("MyLib")
start = 10.0f;
goul = 30.0f;
--補間しよう
t = 0.0;
while t <= 1.0 do
print(Linear(start, end, t));
t = t + 0.1;
end
これはOK、ちゃんと動きます。Lua側で作った関数を呼び出すのであればこれで十分です。ただ、まるでLuaが最初から用意してあるかのように関数を使いたい、いちいちrequire関数を定義するのが面倒だ、そして「Luaで作った関数が遅い」などの理由から、この関数登録をC言語側からする場合もあります。この章の本題はこちらです。
C言語で先程の線形補間関数を実際に作ってみましょう。きっとこんな感じです:
C言語で線形補間関数 float Linear(float start, float end, float t) {
return (1.0f - t) * start + t * end;
};
じゃぁ、これをLuaステートに登録して…と言いたくなるのですが、これは「できません」。え〜〜っと思うのですが、それは当然でして、LuaにしてみればC言語の関数は他国の言語。誰かに翻訳してもらわないとわかりません。この翻訳は普通であれば時間を要します。
そこで、Luaはただ一つだけ自分で解釈できる言語の関数型を定めました。それがこちら:
Luaステートが唯一理解できるC言語の関数 int FuncC(lua_State *L) {
return 0;
};
Luaステートは「引数がlua_Stateへのポインタで戻り値がint型」の関数を「C言語側での関数型」としてLuaが認識でいる唯一の関数として定義しています。
具体的な登録例を挙げながら説明します。上のFuncC関数はC言語側でLuaステートに次のように教えることができます:
C言語の関数をLuaステートに登録 lua_State *L = luaL_newstate();
// Luaステートに関数"Func()"を登録
lua_register(L, "FuncC", &FuncC);
lua_register関数は第3引数のLuaが認識可能な関数を第2引数の名前でLuaステートにグローバル関数として登録します。これでLua側からは、
code.lua FuncC();
と、C言語側で登録した関数を、lua_registerの第2引数の名前で呼び出す事ができるようになります。「じゃ、じゃぁ、引数がfloat3つで、戻り値もfloatなLinear関数を登録するにはどうしたらいいの?」となりますね。それは、次で説明します。
A 引数がfloat3つ、戻り値がfloatのLinear関数をLua側で呼び出す
Luaステートが認識出来る関数はlua_State*型を引数としint型を返す関数のみです。そして、実はこの関数は「グルー関数(Glue Function)」と呼ばれています。意味としては、C言語関数とLua関数の仲立ちをする(くっつける:glue)関数です。
Luaから登録したグルー関数を呼び出すと、引数のlua_State内のスタックにLua側で渡された引数が積まれてグルー関数がコールバックされてきます。
例えば、Linear関数をグルー関数の形で書いたとすると、次のような形になります:
code.lua Linear(10, 30, 0.3);
Luaステートが唯一理解できるC言語の関数 int Linear(lua_State *L) {
// 引数を取得
float start = (float)lua_tonumber(L, 1);
float end = (float)lua_tonumber(L, 2);
float t = (float)lua_tonumber(L, 3);
// 補間計算
float res = (1.0f - t) * start + t * end;
// スタック削除
lua_pop(L, lua_gettop(L));
// スタックに戻り値を積む
lua_pushnumber(L, res);
// 戻り値の数を返す
return 1;
}
Lua内でLinear関数が上のように呼ばれたとすると、C言語側のLinear関数の引数lua_Stateには呼ばれた順に値がスタックに積まれてやってきます。そこでまずその引数を取得します。続いて、その値を用いてやりたい補間計算をします。その結果をLua側に返す必要があるのですが、これもスタックに積みます。その前に今の引数のスタックを削除します。関数の戻り値はその戻り値の個数です。つまり、スタックに複数の値を積めば、複数の値を返せるんです。
このグルー関数を登録してLuaスクリプトを実行すると、確かに次のような出力結果を得られます:
同じような登録をC言語側で行えば、Luaスクリプトで「C言語側できっと登録してくれているに違いない」という暗黙の約束のもと、様々な便利関数を使うことができるようになるわけです。例えば、線形補間だけでなく別の複雑な補間とか、数学関数のみならずゲーム用の様々な関数をC言語側で作り、それをスクリプトを走らせる前にLuaスクリプトに登録しておけば、Luaスクリプトがどんどんと便利になっていくんです。
B グルー関数をどう作ればいいんだろう…む〜
グルー関数の型は厳密に「int Func(lua_State *L)」です(関数名は任意)。これはグローバル領域にある関数です。逆に言えばこれ以外の関数は一切登録できないという事です。
例えば、Lua側でゲーム画面のレイアウトを制御するとしましょう。最初に物を置くウィンドウを得て、それに文字描画用の部品を指定の箇所に置くとします。Luaコードは例えばこんな感じになるでしょうか:
layout.lua --ウィンドウ取得
window = Window.New();
--文字描画用部品を取得、各種パラメータセット
strWindow = StrWindow.New();
strWindow.setWH(400, 200);
strWindow.setStrSpeed(6);
--ウィンドウに文字描画用部品をセット
window.push(strWindow, 100, 200);
とってもわかりやすくて楽しい感じなのですが、これをグルー関数だけで実現するにはC言語側でかなりの準備が必要になります。少しずつ噛み砕いていけば、解決策が見えてくると思います。次の章からその辺りを攻めてみます。