ホーム < ゲームつくろー! < Lua組み込み編


その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言語側でかなりの準備が必要になります。少しずつ噛み砕いていけば、解決策が見えてくると思います。次の章からその辺りを攻めてみます。