Help us understand the problem. What is going on with this article?

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

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 の変換をしてくれているようではある)
voidProc
たまにゲームを作っています。
https://voidproc.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした