状況
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
-
http://melpon.org/wandbox/permlink/kZcqoQVDyKAf4k3j
-
lua.hpp
のインクルードとliblua.a
のリンクがやや特殊になっている点についてはこちらを参照 -> https://github.com/melpon/wandbox/issues/173
-
要点の概説
- C++ から Lua へ
mylib
とobj_type
の2つの定義をluaopen_mylib
をluaL_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
の必要は無い
- 確保してあるメモリー領域の開放処理は Lua ライブラリーが行い、また
References
-
lua-users - wiki - Library With Userdata Example
- 今回の記事のソースコードは比較的モダンな C++ 機能も用いているが、原則的にはこの記事で紹介されているソースコード例を元にしています。