以前はtolua++を使っていましたが、グルーコードを毎回生成するのが意外と不便でした。ヘッダの日付が変わってしまったために全体ビルドがかかってしまったりします。nmakeやバッチファイルを導入してみたり、自動生成したファイルをバージョン管理に含めるべきか悩んだりしました。結局外部定義ファイルよりはcppに直接バインド内容を書けてしまうほうが悩まないかもしれません。
例えば、WindowsのMessageBoxをLuaから使えるようにするコードです。あっけないほど簡単です。わざわざ外部のライブラリを導入する必要もないです。プロジェクトの性質によってはこれで十分用足りるでしょう。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
int MesBox(lua_State *L) | |
{ | |
const char* str = lua_tostring(L, 1); | |
MessageBoxA(nullptr, str, "LuaTest", MB_OK); | |
return 0; | |
} | |
void Bind() | |
{ | |
lua_register(L, "MesBox", MesBox); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
MesBox("A message from Lua") |
ここからが本題です。C++を使うからにはC++っぽいことをしたいわけです。目指す仕様は、
GitHubはここです。
・C++のクラスをバインドしてLuaからインスタンスを作る
・グルーコードでnewからdeleteまで勝手にやってくれる
・ガーベージコレクションの時点でdeleteさせたい
・グルーコードでnewからdeleteまで勝手にやってくれる
・ガーベージコレクションの時点でdeleteさせたい
これを実現したコードが以下です。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class MyClass | |
{ | |
public: | |
int value; | |
}; | |
static const char* myClassName = "MyClass"; | |
static int MyClassSetValue(lua_State *L) | |
{ | |
MyClass** pp = (MyClass**)luaL_checkudata(L, -2, myClassName); | |
(*pp)->value = (int)lua_tointeger(L, -1); | |
return 0; | |
} | |
static int MyClassGetValue(lua_State *L) | |
{ | |
MyClass** pp = (MyClass**)luaL_checkudata(L, -1, myClassName); | |
lua_pushinteger(L, (*pp)->value); | |
return 1; | |
} | |
static int MyClassGC(lua_State *L) | |
{ | |
MyClass** pp = (MyClass**)luaL_checkudata(L, -1, myClassName); | |
delete *pp; | |
return 0; | |
} | |
static int MyClassNew(lua_State *L) | |
{ | |
MyClass** pp = (MyClass**)lua_newuserdata(L, sizeof(MyClass*)); | |
*pp = new MyClass; | |
(*pp)->value = 0; | |
luaL_getmetatable(L, myClassName); | |
lua_setmetatable(L, -2); | |
return 1; | |
} | |
void BindMyClass() | |
{ | |
int r = luaL_newmetatable(L, myClassName); | |
assert(r); | |
lua_pushstring(L, "__index"); | |
lua_pushvalue(L, 1); | |
lua_settable(L, -3); | |
lua_pushstring(L, "__gc"); | |
lua_pushcfunction(L, MyClassGC); | |
lua_settable(L, -3); | |
lua_pushstring(L, "SetValue"); | |
lua_pushcfunction(L, MyClassSetValue); | |
lua_settable(L, -3); | |
lua_pushstring(L, "GetValue"); | |
lua_pushcfunction(L, MyClassGetValue); | |
lua_settable(L, -3); | |
lua_pop(L, 1); | |
lua_register(L, "MyClass", MyClassNew); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local obj = MyClass() | |
obj:SetValue(1234) | |
print("My value is", obj:GetValue()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
My value is 1234 |
C++のBindMyClass関数でLua内にMyClassというクラスをメタテーブルで定義します。MyClassはSetValueとGetValueという2つのメソッドを持ち、__gcでガーベージコレクションの直前に呼ぶ関数があることをLuaに教えます。__indexはやや特殊ですが、MyClassのインスタンスがメソッドを探しに行くテーブルがメタテーブル自身であることを示します。これによってメタテーブル内にメソッドを定義できます。
最後のlua_registerで"MyClass"という名前のLua関数を呼ぶとC++側のMyClassNew関数がインスタンスを生成して返します。クラス名と同じ名前の関数として登録していますが関数名は何でも構いません。MyClassNewではLuaのユーザーデータを生成してC++側で生成したMyClassのポインタを格納し、先ほど作っておいたMyClassのメタテーブルを設定します。
MyClassSetValueとMyClassGetValueはLua側からC++側の変数に値を格納したり取り出したりする所です。せっかくなのでLua5.3の新機能である整数形式を試してみます。lua_Integerはデフォルトで64bitになりました。よって、int型に代入する場合キャストしないとwarningが出ます。
一見複雑ですが、アセンブリを読むような難解さはあれど覚えることはそう多くはないです。一度自力でバインドする方法を試しておくと、たとえ他のオープンソースのバインダーを使う場合でもより理解しやすくなるはずです。
GitHubはここです。
No comments:
Post a Comment