首页 > 解决方案 > 将 C++ 向量绑定到 Lua

问题描述

我正在尝试手动将指针向量从 C++ 绑定到 Lua。

我仅限于具有部分 C++11 支持的编译器,因此不能使用现有的绑定库之一,因为它们现在似乎都使用 C++17。

例如,我有一个包含指向子类的指针列表的类。从 Lua 的角度来看,children 的向量是只读的——我不需要添加、删除等。只需阅读。

class Child
{
public:
    std::string name;
};

class Parent
{
public:
    std::vector <Child *>children;
};
...
    Parent parent;
    Child * m = new Child;
    m->name = "Mary";
    parent.children.push_back(m);
    Child * b = new Child;
    b->name = "Bob";
    parent.children.push_back(b);
...

儿童绑定。

static int Child_name(lua_State * lua) {
    // this should get a point to a Child object and return the name
    lua_pushstring(lua, "child name");
    return 1;
}
static const struct luaL_Reg Child_FunctionList[] = {
    { "name", Child_name },
    { NULL, NULL }
};
static int Child_tostring(lua_State * lua) {
    lua_pushstring(lua, "Child");
    return 1;
}
static const struct luaL_Reg Child_MetaList[] = {
    { "__tostring", Child_tostring },
    { NULL, NULL }
};
void Child_Register(lua_State * lua)
{
    luaL_newlib(lua, Child_FunctionList);
    if(luaL_newmetatable(lua, "ChildMetaTable"))
        luaL_setfuncs(lua, Child_MetaList, 0);
    lua_setmetatable(lua, -2);
    lua_pop(lua, 1);
}

父绑定。

static int Parent_count(lua_State * lua) {
    // used by both the Parent function and metatable __len
    lua_pushinteger(lua, parent.children.size());
    return 1;
}
static int Parent_children(lua_State * lua)
{
    // stack -1=number(1)
    int idx = lua_tonumber(lua, -1);
    Child ** c = static_cast<Child **>(lua_newuserdata(lua, sizeof(Parent *)));
    *c = parent.children[idx];
    luaL_getmetatable(lua, "ChildMetaTable"); // [-0, +1, m]
    lua_setmetatable(lua, -2);
    // return new userdata - does not work
    return 1;
}
static const struct luaL_Reg Parent_FunctionList[] = {
    { "count", Parent_count },
    { "children", Parent_children },
    { NULL, NULL }
};
static int Parent_tostring(lua_State * lua) {
    lua_pushstring(lua, "Parent");
    return 1;
}
static int Parent_index(lua_State * lua) {
    // stack -1=number(1) -2=table
    int idx = lua_tonumber(lua, -1);
    Child * c = parent.children[idx];
    // what to return here?
    return 0;
}
static const struct luaL_Reg Parent_MetaList[] = {
    { "__tostring", Parent_tostring },
    { "__len", Parent_count },
    { "__index", Parent_index },
    { NULL, NULL }
};
void Parent_Register(lua_State * lua) {
    luaL_newlib(lua, Parent_FunctionList);
    if(luaL_newmetatable(lua, "ParentMetaTable"))
        luaL_setfuncs(lua, Parent_MetaList, 0);
    lua_setmetatable(lua, -2);
    lua_setglobal(lua, "Parent");
}

Parent 绑定会生成一个全局表,这是有意的。测试父表:

>print(Parent)
Parent
>print(#Parent)
2
>print(Parent.count())
2

但是尝试访问孩子也不起作用

>c = Parent[1]
>print(c)
Child
>print(type(c))
userdata
>print(c.name())
[string "main"]:8: attempt to index a ChildMetaTable value (global 'c')

我迷路了Parent_index,我需要一个指向 C Parent 对象而不是 Lua 表的指针。我了解该方法是使用 userdata 或 lightuserdata 但看不到如何将类绑定到 Lua 以执行此操作。Child 绑定也是如此,这会导致 ChildMetatable 但没有 Lua 表。

编辑:我在 Parent 下添加了一个子函数,但仍然无法正常工作。lua_setmetatable还更改了从堆栈底部到堆栈顶部的一些索引(负数)

Edit2:这是因为我试图让 Parent:children 既充当表又充当用户数据。所以我可以返回带有 C 对象指针的 userdata 以及带有 __index 的 ChildMetaTable 以确定如何处理子方法。

标签: c++lua

解决方案


我试图做的是同时拥有一个 Lua 表和用户数据。

首先,最好将父__index方法替换为表中为子对象创建新用户数据的函数。

static int Parent_children(lua_State * lua)
{
    int idx = luaL_checkinteger(lua, -1);
    Parent * p = &parent;
    luaL_argcheck(lua, (idx >= 0) && (idx < (int)p->children.size()), 1, "index out of range");

    Child ** pc = static_cast<Child **>(lua_newuserdata(lua, sizeof(Child *)));
    *pc = parent.children[idx];
    luaL_getmetatable(lua, "ChildMetaTable"); // [-0, +1, m]
    lua_setmetatable(lua, -2);
    return 1;
}
static const struct luaL_Reg Parent_MetaList[] = {
    { "__tostring", Parent_tostring },
    { NULL, NULL }
};

由于 child 不是 Lua 表,因此可以从__index方法中调用子函数。Child_FunctionList 保持不变。

static int Child_index(lua_State * lua)
{
    const char * fn_name = luaL_checkstring(lua, -1);
    for(const luaL_Reg * fn = Child_FunctionList; fn->name != NULL; fn++)
    {
        if(strcmp(fn_name, fn->name) == 0)
        {
            lua_pushcfunction(lua, fn->func);
            return 1;
        }
    }
    return 0;
}
static const struct luaL_Reg Child_MetaList[] = {
    { "__tostring", Child_name },
    { "__index", Child_index },
    { NULL, NULL }
};

子方法从用户数据中获取一个 C 指针。

static int Child_name(lua_State * lua)
{
    Child * c = *reinterpret_cast<Child **>(luaL_checkudata(lua, -1, "ChildMetaTable"));
    lua_pushstring(lua, c->name.c_str());
    return 1;
}

最后为孩子注册表值没有意义,因此可以删除但需要注册元表。

void Child_Register(lua_State * lua)
{
    if(luaL_newmetatable(lua, "ChildMetaTable"))
        luaL_setfuncs(lua, Child_MetaList, 0);
    lua_pop(lua, 1);
}

这可能不是最佳解决方案,但它正在走向那里。

编辑:全局parent可以作为luaL_newlib扩展宏中的上值传递,而不是使用全局parent.

luaL_newlibtable(lua, Parent_FunctionList);
lua_pushlightuserdata(lua, parent);
luaL_setfuncs(lua, Parent_FunctionList, 1);
...

static int Parent_children(lua_State * lua) {
Parent * parent= (Parent *)(lua_topointer(lua, lua_upvalueindex(1)));
...

推荐阅读