C++クラスをLuaから使う

Luaスクリプト言語はC言語と親和性が高く、C言語のプログラムにLuaスクリプトを組み込むことによってLuaの関数が呼べたり、逆にLuaからC関数を呼び出すことも可能となっています。前回は、Luaだけでオブジェクト指向を実現するコードを書いてみましたが、今回は、LuaにC++のクラスを公開するコードを書いてみました。

C++のコード:

#include <stdio.h>
#include "lua.hpp"
#include <iostream>

#include <string>
using namespace std;

class CObject {
	string m_Name;
public:
	CObject(string name);
	~CObject();

	string description();
	string getName();
};

CObject::CObject(string name)
{
	m_Name = name;
}

CObject::~CObject()
{
	cout < < m_Name << " is released." << endl;
}

string CObject::description()
{
	return "CObject: " + m_Name;
}

string CObject::getName()
{
	return m_Name;
}

int tolua_CObject_alloc(lua_State *L)
{

	lua_newuserdata(L, sizeof(CObject *)); //2
	lua_newtable(L); //3
	lua_getfield(L, 1, "className"); //4
	const char *name = luaL_checkstring(L, 4);
	lua_getglobal(L, name); //5 -> 4
	lua_remove(L, 4);
	lua_getfield(L, 4, "description");  //5
	lua_setfield(L, 3, "__tostring");
	lua_setfield(L, 3, "__index");
	lua_setmetatable(L, 2);
	return 1;
}

int tolua_CObject_release(lua_State *L)
{
	CObject *p = *(CObject **)lua_touserdata(L, 1);
	delete p;
	return 0;
}

int tolua_CObject_init(lua_State *L)
{
	CObject **p = (CObject **)lua_touserdata(L, 1);
	const char *name = luaL_checkstring(L, 2);
	*p = new CObject(name);
	lua_settop(L, 1);
	return 1;
}

int tolua_CObject_description(lua_State *L)
{
	CObject *p = *(CObject **)lua_touserdata(L, 1);
	lua_pushstring(L, p->description().c_str());
	return 1;
}

int tolua_CObject_getName(lua_State *L)
{
	CObject *p = *(CObject **)lua_touserdata(L, 1);
	lua_pushstring(L, p->getName().c_str());
	return 1;
}

int tolua_CObject(lua_State *L)
{
	lua_newtable(L);
	lua_pushstring(L, "CObject");
	lua_setfield(L, -2, "className");
	lua_pushcfunction(L, tolua_CObject_alloc);
	lua_setfield(L, -2, "alloc");
	lua_pushcfunction(L, tolua_CObject_release);
	lua_setfield(L, -2, "release");
	lua_pushcfunction(L, tolua_CObject_init);
	lua_setfield(L, -2, "init");
	lua_pushcfunction(L, tolua_CObject_description);
	lua_setfield(L, -2, "description");
	lua_pushcfunction(L, tolua_CObject_getName);
	lua_setfield(L, -2, "getName");
	return 1;
}

int main (int argc, const char * argv[]) {
    lua_State *L = lua_open();
	luaL_openlibs(L);

	lua_register(L, "CClass_CObject", tolua_CObject);

	while ( getchar() != 'e') {
		if ( luaL_dofile(L, "hello.lua") ) {
			cout < < lua_tostring(L, -1) << endl;
		}
	}

	lua_close(L);
    return 0;
}

hello.luaのコード

print( "Hello Lua!" )

CObject = CClass_CObject()

obj = CObject:alloc():init("anObject")
print( obj )
print( obj:getName() )
obj:release()

やってることは、前回のLua上でオブジェクトシステムを作ったやり方とほぼ同じで、それをC言語からやっています。重要な点と言えば、クラスを公開する場合は、ライトユーザーデータではなく、フルユーザーデータのほうを使う点です。ライトユーザーデータのほうは、単にポインタを数値的にluaとやり取りするだけなので、メタテーブルを設定することができません。しかし、フルユーザーデータのほうでは、メタテーブルを持つことができます。したがって、フルーユーザーデータを作成し、そこの領域にインスタンスのアドレスを入れることによって、Lua側でメソッドを実行した時にthisポインタを得ることが可能になります。

メタテーブルの”__gc”を設定することによって、Lua側でガベージコレクトされた時に、インスタンスをdeleteすることも可能ですが、Luaから見えないところ(C++側など)でインスタンスが保持されている場合も考えられるので、なんらかの規則を導入しない限りお勧めできません。

ちなみに、前回のオブジェクトシステムと組み合わせて、C++側から公開されたクラスを、Lua側で継承させることができそうですが、この方法ではまだ不十分です。フルユーザーデータは、あくまでテーブルではないので、Lua側で勝手にインスタンス変数を追加することができません。まだ試していないのですが、おそらく”__newindex”のメタテーブルを使って、インスタンス変数の追加を実現することも可能でしょう。

コメント投稿