Sol v2.x
Sol は、C++ 用の Lua バインダです。ヘッダのインクルードのみで利用でき、Boost に依存しない、お手軽に使えるライブラリで個人的におすすめです。Lua バージョン 5.1 以上に対応しており、LuaJIT もいけるみたいです(未確認)。現在活発に開発が行われており、週一ペースくらいで新バージョンがリリースされているようです(→ https://github.com/ThePhD/sol2/releases )。
以下公式のサイトです。
- https://github.com/ThePhD/sol2
-
http://sol2.readthedocs.io/en/latest/index.html
- 公式ドキュメント。"tutorial: quick ‘n’ dirty" を眺めると大体の雰囲気が分かります
ということで、私がよく使う機能を中心に簡単にまとめようと思います。後々便利な機能が見つかったら、都度書き加えていく予定です。参考までに私はこんな環境で使っています。
- Windows 10 64bit
- Visual Studio Community 2015 Update 3
- Lua 5.2.4
導入
https://github.com/ThePhD/sol2/releases から sol.hpp
をダウンロードします。
もしくはこちら。
https://github.com/ThePhD/sol2/tree/develop/single/sol
※リポジトリのルートにある同名のファイルとは内容が違うので注意。
参考:Windows用 Lua バイナリの導入
Lua をビルドするのは面倒だったので Lua Binaries にあるバイナリを使っています。
https://sourceforge.net/projects/luabinaries/files/5.2.4/Windows%20Libraries/Dynamic/
この中の lua-5.2.4_Win64_dll14_lib.zip をダウンロードしました。
まずは最小限のプログラム
Hello World してみます。
#include <sol.hpp>
int main()
{
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::package);
lua.script("print('Hello World')");
}
"Hello World" と表示されたでしょうか? コンパイルできない場合は、sol.hpp
を配置したフォルダをインクルードパスに含めてください。
リンクに失敗する場合は、Lua のライブラリをリンクしてください。Lua 5.2 なら lua52.lib
です。また、実行時に lua52.dll
が必要です。これらについては、Lua 自体の導入に関する他の記事が参考になると思います。
いろいろ試してみる
Lua スクリプトの実行
sol::state lua; //以降省略します
...
// Lua スクリプトを文字列で渡して実行する
lua.script("print(123)"); // => '123'
// 外部の Lua スクリプトを読み込み実行する
lua.script_file("something.lua");
変数
Luaの変数に値を設定してみる
lua["intval"] = 100;
lua["pi"] = 3.14;
lua["some_text"] = "abcdef";
lua.script_file("var.lua")
print(intval) -- => '100'
print(pi) -- => '3.14'
print(some_text) -- => 'abcdef'
Luaの変数の値を取得してみる
lua.script_file("var2.lua")
std::cout << lua["a"].get<int>(); // => '100'
std::cout << lua["b"].get<int>(); // => '200'
std::cout << lua["not_exist"].get_or(999); // => '999'
a = 100
b = 200
c = 300
関数
// C++側で定義した関数をLuaから呼ぶ
lua["func1"] = &func; // どこかに定義された関数
lua["func2"] = [](const int x) { return x * 2; }; // ラムダでもいい
lua.script("print(func2(10))"); // => '20'
// Lua側で定義した関数をC++から呼ぶ
lua.script("function f(x) return x * 3 end");
int ret = lua["f"](100); // => 300
テーブル
lua.script("t = { 10, 20, 30 }");
lua.script("t2 = { a = 'text', b = 123, c = 3.14 }");
std::cout << lua["t"][3].get<int>(); // => '30'
std::cout << lua["t2"]["a"].get<std::string>(); // => 'text'
lua["t3"] = lua.create_table_with("x", 100, "y", 200); // t3 = { x = 100, y = 200 }
std::cout << lua["t3"]["y"].get<int>(); // => '200'
入れ子になっているテーブルも lua["a"]["b"]["c"]["d"]
という感じでアクセスできます。
// C++の関数へLuaのテーブルを渡す
lua["f"] = [](sol::table& t) -> int { return t[1].get<int>(); };
lua.script("print(f({10, 20, 30}))"); // => '10'
クラス
下記のようにして C++ のクラスを Lua 側でも使用することができます。
詳細については公式ドキュメントの "C++ in Lua" をご覧ください。
例1:C++クラスのインスタンスをLua側で生成し使用する
#include <sol.hpp>
int main()
{
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::package);
// クラスAをC++とLua間でやりとりしたい
class A
{
public:
A(const int val = 0) : val_(val) {}
int f() {
return val_ * 100;
}
int getVal() {
return val_;
}
void setVal(const int val) {
val_ = val;
}
int x = 0;
private:
int val_;
};
// ユーザー定義型の登録
lua.new_usertype<A>(
// 型名
"A",
//コンストラクタ
sol::constructors<sol::types<>, sol::types<const int>>(),
// メンバ関数
"f", &A::f,
// メンバ変数
"x", &A::x,
// プロパティ
"val", sol::property(&A::getVal, &A::setVal),
// ラムダ関数も使用可
"y", [](A& a, const int n) ->int { return a.x * n; }
);
lua.script_file("a.lua");
}
a = A.new(100)
print(a:f()) -- => '10000'
a.val = 456
print(a.val) -- => '456'
print(a:f()) -- => '45600'
a.x = 123
print(a.x) -- => '123'
print(a:y(200)) -- => '24600'
例2:関数に渡してみる
#include <sol.hpp>
#include <memory>
struct B
{
void hello() { std::cout << "hello\n"; }
};
void f1(B* b)
{
std::cout << "f1\n";
b->hello();
}
int main()
{
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::package);
lua.new_usertype<B>(
"B",
"hello", &B::hello);
lua["f1"] = &f1;
// C++関数f1にLua側で生成したインスタンスを渡す
lua.script_file("b.lua");
// Lua側の関数f2にC++側で生成したインスタンスを渡す
B b;
lua["f2"](b); //値渡し
lua["f2"](&b); //ポインタ渡し
lua["f2"](std::ref(b)); //参照渡し
// shared_ptrも渡せる
std::shared_ptr<B> pb(new B());
lua["f2"](pb);
}
obj = B.new()
f1(obj)
function f2(b)
print("f2")
b:hello()
end
コルーチン
function f(arg)
coroutine.yield(arg + 1)
coroutine.yield(arg + 2)
coroutine.yield(arg + 4)
return 0
end
#include <sol.hpp>
int main()
{
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::coroutine);
lua.script_file("coro.lua");
sol::thread th = sol::thread::create(lua.lua_state());
sol::coroutine coro = th.state()["f"];
std::cout << coro(1).get<int>() << "\n"; // => '2'
std::cout << coro().get<int>() << "\n"; // => '3'
std::cout << coro().get<int>() << "\n"; // => '5'
std::cout << coro().get<int>() << "\n"; // => '0'
// あるいは
//while (coro.runnable())
//{
// // ※コルーチンの引数は、初回に渡したものはコルーチン関数の引数となり、以降渡したものはyieldの戻り値となる
// std::cout << coro(1).get<int>() << "\n";
//}
}
環境
sol 2.17 から、参照する環境テーブルを指定できるようになりました。
examples/environments.cpp
sol::state lua;
lua.script("a = 123");
// 新しい環境テーブルを作成
sol::environment env1(lua, sol::create);
lua.script("b = 456", env1);
std::cout << lua["a"].get_or(-1) << "\n"; // => '123'
std::cout << env1["a"].get_or(-1) << "\n"; // => '-1'
std::cout << lua["b"].get_or(-1) << "\n"; // => '-1'
std::cout << env1["b"].get_or(-1) << "\n"; // => '456'
まとめてみて雑感など
- ヘッダ 1 個で使えるのがお手軽でとても良い
- C++11/14 で書かれているのも使いやすいポイント(ラムダ関数でバインドできたり)
- Lua の環境とテーブルに関するインターフェースが統一されていて使いやすい
- wstring をうまく扱う方法が分からない(Sol がスタックへの push/pop 時に string/wstring の変換をしてくれているようではある)