tags:

views:

1080

answers:

4

I have a Lua userdata object with a certain metatable type (e.g. "stackoverflow.test"). From C code, I want to be able to check exactly which type it is, and behave differently depending on the results. Is there a nice handy function (rather like luaL_checkudata, but without erroring if the answer isn't what you want) that let's me query the metatable type name of the userdata? If not, I guess I need to use lua_getmetatable, but then I'm a bit unclear how I determine the name of the metatable that's just been added to the stack.

Just to clarify: I'm using Lua 5.1, where the behaviour of luaL_checkudata was changed. I understand that in 5.0 it didn't used to error.

A: 

I've just looked at the source code to the luaL_checkudata function, and it basically fetches the userdata object's metatable using lua_getmetatable. It then fetches the given type name from the registry using lua_getfield, and does a lua_rawequal call to compare them.

andygeers
+4  A: 

You could always store a marker field in the metatable with a light userdata value unique to your module.

static const char *green_flavor = "green";
...
void my_setflavor(lua_State *L, void *flavor) {
  lua_pushlightuserdata(L,flavor);
  lua_pushlstring(L,"_flavor");
  lua_rawset(L,-3);
}

void my_isflavor(lua_State *L, void *flavor) {
  void *p = NULL;
  lua_pushlstring(L,"_flavor");
  lua_rawget(L,-2);
  p = lua_touserdata(L,-1);
  lua_pop(L,1);
  return p == flavor;
}

Then you can use my_setflavor(L,&green_flavor) to set the _flavor field of the table at the top of the stack, and my_isflavor(L,&red_flavor) to test the _flavor field of the table at the top of the stack.

Used this way, the _flavor field can only take on values that can be created by code in the module that has the symbol green_flavor in scope, and looking up the field and testing its value only required one table lookup aside from retrieval of the metatable itself. Note that the value of the variable green_flavor does not matter, since only its address is actually used.

With several distinct flavor variables available to use as sentinal values, the _flavor field can be used to distinguish several related metatables.

All of that said, a natural question is "why do this at all?" After all, the metatable could easily contain all of the information it needs to get the appropriate behavior. It can readily hold functions as well as data, and those functions can be retrieved and called from C as well as Lua.

RBerteig
+1  A: 

The userdata has to have a metatable, so grab that; then look up the name you want in the registry. If the two objects are identical, you've found the type you're looking for.

You could dispatch on this type in C code, but allow me gently to suggest that you designate a field of the metatable instead. A function stored in the metatable should do the job, but if not, if you absolutely have to switch in C code, then pick a name, use that to index into the metatable, and assign each metatable a small integer you can switch on.

meta1.decision = 1
meta2.decision = 2
meta3.decision = 3

and then in your C code

if (lua_getmetatable(L, 1)) {
  lua_getfield(L, -1, "decision");
  if (lua_isnumber(L, -1)) {
    switch ((int) lua_tonumber(L, -1)) {
       case 1: ... ; break;
       case 2: ... ; break;
       case 3: ... ; break;
    }
    return 0;
  }
}
return luaL_error(L, "Userdata was not one of the expected types");
Norman Ramsey
+1  A: 

You'll use lua_getmetatable and lua_equal for testing that the tables are the same.

In my opinion, Lua should give more support to this kind of type-extending things. As of now, it's really on the responsibility of the Lua/C(++) wrapper system to do that.

In a wrapper I've done recently (as part of a commercial project) I do class::instance(L,index) to get userdata pointers of a particular type. In other words, that method checks that it's userdata and that the metatable is also right. If not, it returns NULL.

The way Lua could help all this is if the metatable had a standard field for extended type information (s.a. __type). This could be used so that type() itself would return "userdata", "xxx" (two values, currently returning only one). This would remain compatible with most of current code. But this is just hypothetical (unless you do a custom type() and implement this on your own).

akauppi