3
votes

I'm trying to manually bind a vector of pointers from C++ to Lua.

I'm limited to a compiler which has partial C++11 support, so can't use one of the existing binding libraries since they all seem to use C++17 now.

For example, I have a class which contains a list of pointers to a child class. The vector of children is read only from the Lua point of view - I don't need add, remove etc. Just read.

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);
...

Child binding.

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);
}

Parent binding.

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");
}

The Parent binding results in a global table, which is intentional. Testing the Parent table:

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

But trying to access the children doesn't work as well

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

I get lost in Parent_index, where I need a pointer to the C Parent object rather than the Lua table. I understand the method is to use userdata or lightuserdata but can't see how to bind the class to Lua in order to do this. Same for the Child binding, which results in a ChildMetatable but no Lua table.

Edit: I've added in a children function under Parent, but still not working. Also changed some of the indexes for lua_setmetatable from bottom of stack to top of stack (negative)

Edit2: It's because I'm trying to have Parent:children act both as a table and as userdata. So I can return userdata with the C object pointer along with the ChildMetaTable with __index to determine what to do with the child methods.

1
You should use lua_pushinteger instead of lua_pushnumber in Parent_count, as it is weird to have a floating point number as a count. - prapin
You probably don't want to use lua_pushlightuserdata in Parent_index, as light user data have no metatable. You need to create full metadata using lua_newuserdatauv. - prapin
Thank you. I've changed to lua_pushinteger and also changed the lua_setmetatable to use indexes from the stack top. I've tried creating userdata and then setting to the metatable in Parent_children, but still not there yet. - user1139455

1 Answers

0
votes

What I was trying to do was to have both a Lua table and userdata at the same time.

First the parent __index method is best replaced by function in the table that creates new userdata for the child object.

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 }
};

Since the child is not a Lua table, the child functions can be called from the __index method. Child_FunctionList stays the same.

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 }
};

And the child methods get a C pointer from the userdata.

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;
}

And finally registering tables values for the child doesn't make sense, so can be removed but the metatable needs to be registered.

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

This may not be the optimal solution, but it is heading there.

Edit: The global parent can be passed as an upvalue in the luaL_newlib macro is expanded, rather than using the global 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)));
...