C++ に Lua を良い感じに組み込むためのライブラリ Sol v2.x の使い方まとめ

More than 1 year has passed since last update.


Sol v2.x

Sol は、C++ 用の Lua バインダです。ヘッダのインクルードのみで利用でき、Boost に依存しない、お手軽に使えるライブラリで個人的におすすめです。Lua バージョン 5.1 以上に対応しており、LuaJIT もいけるみたいです(未確認)。現在活発に開発が行われており、週一ペースくらいで新バージョンがリリースされているようです(→ https://github.com/ThePhD/sol2/releases)。

以下公式のサイトです。

ということで、私がよく使う機能を中心に簡単にまとめようと思います。後々便利な機能が見つかったら、都度書き加えていく予定です。参考までに私はこんな環境で使っています。


  • 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")


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'


var2.lua

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側で生成し使用する


main.cpp

#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.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);
}


b.lua

obj = B.new()

f1(obj)

function f2(b)
print("f2")
b:hello()
end



コルーチン


coro.lua

function f(arg)

coroutine.yield(arg + 1)
coroutine.yield(arg + 2)
coroutine.yield(arg + 4)
return 0
end


coro.cpp

#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 の変換をしてくれているようではある)