将现有的C++对象传递给Lua

3

我正在研发自己的游戏引擎,想要添加对Lua脚本的支持以编写游戏行为。但是,我目前遇到了C++类在Lua中的问题。我知道如何通过Lua在堆上创建一个新的类实例,但那可能不是我想要做的。

我更希望将已存在于C++中的对象传递到Lua中,并在脚本内部使用它。例如:引擎拥有一个怪物的实例,我想运行一个脚本来判断怪物是否看到了玩家,如果看到了,则怪物会攻击玩家。

我找到的唯一解决方案是:将现有的C++对象传递到Lua并调用传递对象的成员函数 - 然而,原始帖子使用了需要boost的luabind(我不想使用)。

因此,我的问题如下:

如何将在C++中已分配到堆中的对象传递到Lua脚本中?(不使用luabind)

这种方法是否正确?我所找到的大多数答案都倾向于回答“如何在Lua中创建C++类的实例”,而不是仅传递它,这让我想知道我的想法是否正确。

注意:我不介意使用诸如luabind之类的工具,只是不想使用依赖于外部库(例如boost)的工具。如果有任何简单的解决方案,我将非常乐意使用。


你需要为类定义一个元表并编写一系列函数... - Kerrek SB
如果你的对象已被销毁,但是Lua仍然持有它,这种情况应该如何处理? - mniip
3个回答

2
如果您想要真正独立的绑定,这里提供了经典的C语言样板代码。只需将“Object”替换为您的类名并实现// { }块即可。将push_Object从静态变为公共的将为您提供通用的现有对象推入器,该推入器还会在元表中缓存对象(否则,多次推入将创建许多明显存在垃圾回收问题的不同用户数据)。
如果您想要,也可以将其转化为C++或库文件形式,但我个人不这样做,因为如果它是库文件,添加例如__newindex-to-environment代理和其他怪癖将不那么直观。实际上,所有样板都在push_mtpush_Objectforget_Objectcheck_Object中,其他所有内容都需要进行微调。
请注意,这仅绑定了一个单一的类,而不是所有类。
// { class Object { ... } }

static const char *tname = "Object";

static void push_Object(lua_State *L, Object *object);
static Object *check_Object(lua_State *L, int i);

static int
l_gc(lua_State *L)
{
    Object **ud = luaL_checkudata(L, 1, tname);

    if (*ud) {
        // { delete *ud }
        *ud = NULL;
    }
    return 0;
}

static int
l_tostring(lua_State *L)
{
    Object **ud = luaL_checkudata(L, 1, tname);

    lua_pushfstring(L, "%s: %p", tname, *ud);
    return 1;
}

static int
l_new(lua_State *L)
{
    Object *object = NULL; // { = new Object }

    push_Object(L, object);
    return 1;
}

static int
l_method(lua_State *L)
{
    Object *object = check_Object(L, 1);
    lua_Integer int_arg = luaL_checkinteger(L, 2);
    const char *str_arg = luaL_checklstring(L, 3, NULL);

    // { object->method(int_arg, str_arg) }

    return 0;
}

static const luaL_Reg lib[] = {
    // functions
    { "new",    l_new    }, // () -> object

    // methods
    { "method", l_method }, // (object, int, string) -> none

    { NULL, NULL },
};
static lua_CFunction first_m = l_method;

static void
push_mt(lua_State *L)
{
    if (luaL_newmetatable(L, tname)) {
        size_t m = 0; while (first_m != lib[m].func) m++;
        lua_createtable(L, 0, 0);
        luaL_register(L, NULL, &lib[m]);
        lua_setfield(L, -2, "__index");

        lua_pushcfunction(L, l_tostring);
        lua_setfield(L, -2, "__tostring");

        lua_pushcfunction(L, l_gc);
        lua_setfield(L, -2, "__gc");

        lua_pushstring(L, tname);
        lua_setfield(L, -2, "__metatable");

        // mt.objects = setmetatable({ }, { __mode = "v" })
        lua_createtable(L, 0, 0);
        lua_createtable(L, 0, 1);
        lua_pushstring(L, "v");
        lua_setfield(L, -2, "__mode");
        lua_setmetatable(L, -2);
        lua_setfield(L, -2, "objects");
    }
}

static void
push_Object(lua_State *L, Object *object)
{
    int top = lua_gettop(L);

    push_mt(L);
    lua_getfield(L, -1, "objects");
    // top+1 = mt
    // top+2 = mt.objects

    // ud = mt.objects[object]
    lua_pushlightuserdata(L, object);
    lua_gettable(L, top+2);

    if (lua_isnil(L, -1)) {
        lua_pop(L, 1);

        Object **ud = lua_newuserdata(L, sizeof(*ud));
        *ud = object;

        // setmetatable(ud, mt)
        lua_pushvalue(L, top+1);
        lua_setmetatable(L, -2);

        // mt.objects[object] = ud
        lua_pushlightuserdata(L, object);
        lua_pushvalue(L, -3);
        lua_pushvalue(L, top+2);
    }

    // return ud
    lua_replace(L, top+1);
    lua_settop(L, top+1);
    return; // ud at top
}

static void
forget_Object(lua_State *L, Object *object)
{
    int top = lua_gettop(L);

    push_mt(L);
    lua_getfield(L, -1, "objects");
    // top+1 = mt
    // top+2 = mt.objects

    // ud = mt.objects[object]
    lua_pushlightuserdata(L, object);
    lua_pushnil(L);
    lua_settable(L, top+2);

    lua_settop(L, top);
}

static Object *
check_Object(lua_State *L, int i)
{
    Object **ud = luaL_checkudata(L, i, tname);
    Object *object = *ud;

    if (object == NULL)
        luaL_error(L, "%s is finalized", tname);

    return object;
}

int
luaopen_Object(lua_State *L)
{
    push_mt(L); // register tname

    lua_createtable(L, 0, sizeof(lib)-1);
    luaL_register(L, NULL, lib);
    return 1;
}

0

https://github.com/Rapptz/sol

Sol是一个完美的库,支持C++11+语义,包括使用模板传递类类型。如果我是你,我会查看Sol的源代码,并尝试复制其对象和数据传递方法--Lua的“元”功能很可能是建立在其userdata结构上的,因此我建议您从那里开始查找。

顺便说一下,Sol仅依赖于Lua,并且只需要头文件即可。只要您使用Lua 5.2(而不是5.3,这是社区的一个大问题,但不应该有影响,因为我相信LuaJIT也是5.2),就可以直接使用它。


LuaJIT是基于5.1版本的,而不是5.2。 - brianush1

0
大多数我找到的答案都是回答“如何在Lua中创建C++类的实例”,而不是仅仅传递它,这让我想知道我的想法是否正确。
这些也是你问题的答案。你不能在Lua中创建C++类。任何类的构造都将在C++代码中完成。
如果你不使用绑定库,那么你需要成为Lua的C API和元表的导出(不用担心,这并不难)。
你将C++对象作为“userdata”暴露给Lua。然后,你提供一个带有__index元方法的元表,允许你编写C++处理程序,当对象被索引时,例如当访问属性时(例如pt.x)或调用方法时(例如entity:attack(target))。你还需要一个__gc元方法,以便在相应的Lua userdata被垃圾回收时删除C++对象。

编程中可能会有很多样板代码,这就是许多C++绑定库所要处理的内容。我从未使用过其中任何一个,因此无法推荐。然而,由于泄漏抽象原则,我强烈建议您自己尝试一下,以便了解其工作原理。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接