Reputation: 7237
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.
Upvotes: 1
Views: 1895
Reputation: 4271
If you want to re-use a lua_CFunction
for multiple related userdata
types, you have to overcome two problems:
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
.
"A.B.C"
is assumed to also be a valid "A.B"
userdata, and a
valid "A"
userdata. Checking is just a matter of comparing a
prefix of the real type name with the requested userdata type
name.luaL_checkudata()
for multiple specified type names until one of those calls
succeeds.https://github.com/diegonehab/luasocket/blob/master/src/auxiliar.c (and https://github.com/siffiejoe/lua-moon#moon_defcast as well) maintain a set of supported type names in the userdata's metatable. If the requested type doesn't match exactly, it's just an additional table lookup to check whether the type is supposed to be compatible.
This is also the approach used in @Rochet2's answer to this
question, with an additional twist: It re-uses the chained __index
metamethod tables to make the set of compatible types more manageable.
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.
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:
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
.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.
Upvotes: 2
Reputation: 1156
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.
Upvotes: 0