1
votes

I have the following scenario:

One class named "Base" and second class named "Derived" which derives from "Base". I use metatables to support inheritance in Lua. However I need a solution for checking if passed userdata inherits from specified class.

static int BaseGetMyVar(lua_State *state)
{
    Base* base = *(Base**)luaL_checkudata(state, 1, "Base");
    //Base* base = *(Base**)lua_touserdata(state, 1);
    lua_pushinteger(state, base->GetMyVar());
    return 1;
}

When I pass "Derived" udata to this method I get:

HelloWorld.lua:17: calling 'getMyVar' on bad self (Base expected, got userd
ata)

How do I check inheritance? So I can pass either Base or Derived.

Current solutions I've found:

http://lua-users.org/lists/lua-l/2005-06/msg00000.html

http://lua-users.org/lists/lua-l/2007-04/msg00324.html

https://github.com/diegonehab/luasocket/blob/master/src/auxiliar.c

Proposal by siffiejoe(Thanks)

https://github.com/siffiejoe/lua-moon#moon_defcast

Another solution found by accident:

http://lua-users.org/lists/lua-l/2013-06/msg00492.html

My own solution(I like to own things )

void* luaL_checkclass(lua_State *L, const char *classname, int objidx)
{
    if(!luaL_getmetafield(L, objidx, "__index"))
        return NULL;
    lua_getfield(L, -1, "__name");
    if(lua_type(L, -1) != LUA_TSTRING)
        return NULL;
    char const* basename = lua_tostring(L, -1);
    lua_pop(L, 2);
    if(strcmp(basename, classname) != 0)
        luaL_typeerror(L, objidx, classname);
    return lua_touserdata(L, objidx);
}

I came up with my own idea. This requires metatable to have field __name(Lua 5.3 implements this, but you can still add this field on your own in previous versions).

More detailed:

Each metatable I register in Lua owns __index field which is either assigned to self or if it has a base class it is assigned to base class. The table at __index owns __name field which is the name of the class... we can just compare the names.

If we pass Derived object to Base method now it will work just fine.

2
Another one for your list: github.com/siffiejoe/lua-moon#moon_defcast. The implementation is simliar to the LuaSocket one, but the luaL_checkudata() replacement automatically calls the registered function to cast the pointer to the correct type.siffiejoe
Thank you @siffiejoe (Philipp). I've added my own solution which works for me. If you have some suggestions, please let me know.Konrad

2 Answers

2
votes

If you want to re-use a lua_CFunction for multiple related userdata types, you have to overcome two problems:

Accepting multiple userdata types

The luaL_checkudata() function that is normally used for type-safe access to a userdata's memory only checks for one specific userdata type. All mentioned approaches therefore use a different function to check the arguments of the lua_CFunction.

Getting a proper pointer to the userdata's memory

Even when a userdata belongs to a group of related userdata types, the memory layout of the different types is usually slightly different (otherwise it wouldn't make much sense to use different userdata types at all). So the second problem is to get a pointer that is compatible with the requested userdata type.

Most of the approaches mentioned above ignore this problem because in C you can get away with it if you are careful how you define your related userdata types. The C standard guarantees that a pointer to a struct has the same value as a pointer to its first member. So if you have

 struct A { ... };
 struct B { A a; ... };
 struct C { B b; ... };

a pointer to struct C is automatically also a valid pointer to struct B and struct A.

However if you cannot guarantee this particular memory layout, or if you use C++ with multiple inheritance, or virtual inheritance, or just an unfortunate choice of virtual methods (see this SO answer), you'll likely have to adjust the pointer values to make them valid as pointers to a related userdata type.

https://github.com/siffiejoe/lua-moon#moon_defcast stores simple callback functions that you can use to adjust the pointer value to the required type. E.g. for class B : public A ... you would use return (void*)(A*)(B*)p; if p really is a void pointer to a B object that should be passed to a function that expects a void pointer to an A object.

"Own" solution suggested in the question

The approach added in an edit to the question ignores problem 2. It tries to solve problem 1 (identification of compatible userdata types) by storing the userdata type name in the metatable (which also serves as the __index metamethod). In Lua 5.3 the luaL_newmetatable() function will do this for you. The given code snippet only works for two cases:

  1. A Derived userdata type uses the Base metatable as its __index metamethod, i.e. the derived userdata type does not have methods on its own, and so doesn't have its own __index.
  2. For the Base object itself.

If you add more levels (e.g. a MoreDerived type), or if you need methods specific to the Derived type, this simple approach won't work anymore. You'd have to walk the __index metamethods chain upward (assuming inheritance is handled using chained __index metamethods) until you find the type name you are looking for.

0
votes

One easy way I can think of could be that the tables look like this:

Base = {Base = true}
Base.__index = Base
Derived = setmetatable({Derived = true}, Base)
Derived.__index = Derived

obj = setmetatable({}, Derived)

Now it is simple to check if obj inherits Base. You just do obj.Base and this returns true if Base is inherited. In C you can use the methods lua_setfield and lua_getfield to set and get the value of the keys Base and Derived from the metatable.

There are multiple other ways to go about doing this.