LoginSignup
9
10

More than 5 years have passed since last update.

C++ & Lua : C++ のネイティブデータ型を Lua へ提供し、操作する例

Last updated at Posted at 2016-01-10

状況

C++ に Lua を組み込み実行時に Lua スクリプトを C++ コードから呼び出して処理するプログラムにおいて、 C++ のネイティブデータ型を Lua へ提供し、これを操作したい。

具体的な例

例として次に挙げるような C++ のユーザー定義型を要素とする std::vector コンテナー型を Lua へ提供し、操作させたいとする。

struct f32vec4
{
  float x, y, z, w;
  f32vec4( const float a = 0, const float b = 0, const float c = 0, const float d = 0 )
    : x( a ), y( b ), z( c ), w( d )
  { }
};
using native_data_type = std::vector< f32vec4 >;

Lua コードからは次のように制御したいとする

local obj = mylib.make_obj()
local nan = 0 / 0
obj:push( 1.23, 3.45, 6.78, nan  )
obj:push( 12.3, 34.5, 67.8, 90.1 )

実際にはmake_objで引数を与えて std::vector を一定の要素数で生成したり reserve したり、あるいは C++ コード側から呼び出す Lua コードの関数の return として native_data_type を受け取るなど様々なニーズが考えられるが、この記事では C++ のネイティブデータ型 native_data_type を Lua へ提供し、 Lua で push により emplace_back し、 Lua がオブジェクトを破棄する時点で native_data_type が格納している値を C++ コードで出力するに留める。

コード例

main.cxx

#include <lua.hpp>
#include <iostream>
#include <vector>
#include <new>

struct f32vec4
{
  float x, y, z, w;
  f32vec4( const float a = 0, const float b = 0, const float c = 0, const float d = 0 )
    : x( a ), y( b ), z( c ), w( d )
  { }
};
using native_data_type = std::vector< f32vec4 >;

auto luaopen_mylib( lua_State* L )
{
  static const luaL_Reg obj_definitions[] =
  { { "method_test"
    , []( auto L )
      {
        std::cout << "obj.method_test : " << luaL_checkudata(L, 1, "obj_type") << "\n";
        return 0;
      }
    }
  , { "push"
    , []( auto L )
      {
        reinterpret_cast<native_data_type*>(luaL_checkudata(L, 1, "obj_type"))->emplace_back
        ( luaL_checknumber(L, -4)
        , luaL_checknumber(L, -3)
        , luaL_checknumber(L, -2)
        , luaL_checknumber(L, -1)
        );
        std::cout << "obj.push" << "\n";
        return 0;
      }
    }
  , { nullptr, nullptr }
  };

  static const luaL_Reg mylib_definitions[] =
  { { "make_obj"
    , []( auto L )
      {
        std::cout << "mylib.make_obj\n";
        auto p = lua_newuserdata( L, sizeof( native_data_type ) );
        new( p ) native_data_type(); // placement new
        luaL_setmetatable( L, "obj_type" );
        return 1;
      }
    }
  , { nullptr, nullptr }
  };

  luaL_newlib( L, mylib_definitions ); // stack: mylib

  luaL_newmetatable(L, "obj_type" ); // stack: mylib meta
  luaL_newlib(L, obj_definitions );
  lua_setfield(L, -2, "__index" ); // stack: mylib meta

  lua_pushstring( L, "__gc" );
  lua_pushcfunction
  ( L
  , []( auto L )
    {
      const auto p = luaL_checkudata( L, 1, "obj_type" );
      std::cout << "obj.__gc : " << p << "\n";
      std::cout << "=== print data ===\n";
      for ( const auto& e : *reinterpret_cast< native_data_type* >( p ) )
        std::cout << "  " << e.x << " " << e.y << " " << e.z << " " << e.w << "\n";
      std::cout << "=== === === ===\n";
      return 0;
    }
  ); // stack: mylib meta "__gc" fptr
  lua_settable(L, -3); // stack: mylib meta

  lua_pop(L, 1); // stack: mylib

  return 1;
}

auto main() -> int
{
  lua_State *L = luaL_newstate();
  luaL_openlibs(L);

  luaL_requiref( L, "mylib", luaopen_mylib, 1 );
  lua_pop( L, 1 ); // requiref leaves the library table on the stack

  luaL_dofile( L, "a.lua" );

  lua_close( L );
}

a.lua

local obj = mylib.make_obj()

obj:method_test()

local nan = 0 / 0

obj:push( 1.23, 3.45, 6.78, nan  )
obj:push( 12.3, 34.5, 67.8, 90.1 )

実行結果

mylib.make_obj
obj.method_test : 0xbcc358
obj.push
obj.push
obj.__gc : 0xbcc358
=== print data ===
  1.23 3.45 6.78 -nan
  12.3 34.5 67.8 90.1
=== === === ===

Wandbox

要点の概説

  • C++ から Lua へ mylibobj_type の2つの定義を luaopen_mylibluaL_requiref(L, "mylib", luaopen_mylib, 1); で与えている
  • mylib には obj_type を生成する make_obj メソッドを mylib_definitions で定義している
    • lua_newuserdata は C の malloc 挙動のようなものでメモリー領域の確保しか行わないので、 new( p ) native_data_type() として placement new により確保済みのメモリー領域に native_data_type を構築している
  • obj_type には C++ レベルでの emplace_back を行う push メソッドとより簡単なテスト用のメソッド method_test を定義している
  • obj_type には追加で lua_pushstring lua_pushfunction 等で __gc メソッドも定義し、 Lua の GC がオブジェクトを破棄する際に C++ コードレベルで native_data_type として保持する要素を出力している
    • 確保してあるメモリー領域の開放処理は Lua ライブラリーが行い、また native_data_type の構築も placement new で行っているため delete の必要は無い

References

  • lua-users - wiki - Library With Userdata Example
    • 今回の記事のソースコードは比較的モダンな C++ 機能も用いているが、原則的にはこの記事で紹介されているソースコード例を元にしています。
9
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
10