lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


It was thus said that the Great Ranier Vilela once stated:
> Hi hackers,

  Hello.

> Is it possible to create and use a userdata value, this way?

  What follows is your code, but I've added some commentary.

typedef struct MyItem {
   int size;
} MyItem;

typedef struct MyValue {
  const char * name;
  MyItem * item;
} MyValue;

static int lua_myvalue_new(lua_State *L) {
  MyValue * value;
  const char * name;
  size_t l;
  int n;

  name = luaL_checklstring(L, 1, &l);
  // The following check is not needed---luaL_checklstring() will do the
  // check for you and raise an error is the item isn't a string.  Also,
  // given that you do nothing with the size of the string, you can use
  // luaL_checkstring() instead.
  
  // if (name == NULL) {
  //   return 0;
  // }

  n = luaL_checkinteger(L, 2);
  luaL_argcheck(L, n >= 1, 2, "invalid size");

  value = (MyValue *) lua_newuserdata(L, sizeof(MyValue));
  // Again, no need for the following check as lua_newuserdata() will raise
  // an error if it can't allocate memory.
  
  // if (value == NULL) {
  //   return 0;
  // }

  // The issue with the following line is assigning the name to the
  // structure.  Strings are subject to garbage collection.  But here, Lua
  // has no idea that there's a reference to a string, so the string itself
  // may go away.  One way to handle this is to strore a reference to the
  // string in the registry; another way is to allocate memory and create a
  // copy, but then the __gc metamethod of MYVALUE_REGISTRY will have to
  // free this memory.

  value->name = name;

  luaL_getmetatable(L, MYVALUE_REGISTRY);
  lua_setmetatable(L, -2);

  value->item = (MyItem *) lua_newuserdata(L, sizeof(MyItem));
  // Again, the following check isn't needed.
  
  // if (value->item == NULL) {
  //     return 0;
  // }
  
  // Like with the name, this creates a value that Lua can garbage collect,
  // but Lua doesn't know you have a reference to it.  You may need to store
  // a reference to it in the registry.

  value->item->size = n;
  luaL_getmetatable(L, UIMYITEM_REGISTRY);
  lua_setmetatable(L, -2);

  // You can solve this several ways---one is to call lua_pop(L,1) to pop
  // MyItem off the stack, or to use lua_pushvalue(L,-2) to copy MyValue to
  // the top of the stack.  Another way is to generate the uservalues in
  // reverse order, thus avoiding the lua_pop() or lua_pushvalue() calls.
  
  return 1; /* Must return value (MyValue) */
}

> Howto return to Lua MyValue struct (userdata)?

  Mentioned in the comment above.
  
> MyItem needs to be allocated by malloc rather than lua_newuserdata?

  That is one way to do it.  But then the __gc metamethod of
MYVALUE_REGISTRY will have to explicitely free it.  Also doing it this way
prevents Lua from garbage collecting it if there's no reference to it.

  Now my question to you is---*how* are you going to use MyValue in Lua?  Is
it something that is opaque and not modified through Lua?  If so, this is
not a bad approach.  If you are going to modify it through Lua, then you may
want to use a different approach and use Lua tables until you can't.  I use
this method at work, and while I can't show the code, I can tell how it's
implemented (simplified):

  I have the following structure:
  
  	struct data
  	{
  	  char     *key;
  	  char     *picid;
  	  uint32_t  pictypes; // bitflags for image types, GIF, PNG, JPEG, etc.
  	  char     *info[];   // an array of strings
  	  size_t    numinfo;  // number of info entries
  	};

  In the system, I have some C functions that look like:
  
  	struct data const *record_get(char const *key);
  	int                record_put(char const *key,struct data const *data);
  	
  At the boundary between Lua and the rest of the system (in C/C++), I
convert this to/from a Lua table, since it can be manipulated and it's
easier (and less code) to write if I do so.  The record_get() function for
Lua looks something like:

	static int lua_record_get(lua_State *L)
	{
	  struct data const *info = record_get(luaL_checkstring(L,1));
	  
	  if (info == NULL)
	    return 0;
	  lua_createtable(L,0,4);
	  lua_pushvalue(L,1);
	  lua_setfield(L,-2,"key");
	  lua_pushlstring(L,info->picid);
	  lua_setfield(L,-2,"picid");
	  
	  // turn the pictypes field into an array for easy processing in Lua
	  
	  lua_createtable(L,0,0);
	  // blah blah
	  lua_setfield(L,-2,"pictypes");
	  
	  lua_createtable(L,0,0);
	  // blah blah
	  lua_setfield(L,-2,"info");

	  return 1;
	}

  And the reverse:
  
	static int lua_record_set(lua_State *L)
	{
	  struct data info;
	  int         rc;
	  
	  info.key = luaL_checkstring(L,1);
	  luaL_checktype(L,2,LUA_TTABLE);
	  lua_getfield(L,2,"picid");
	  info.picid = luaL_checkstring(L,-1);
	  lua_getfield(L,2,"pictypes");
	  
	  // code to loop through Lua table to get pictypes
	  
	  lua_getfield(L,2,"info");
	  
	  // code to loop through Lua table to get info
	  
	  rc = record_put(info.key,&info);
	  lua_pushboolean(L,rc == 0);
	  lua_pushinteger(L,rc);
	  return 2;
	}

  I don't have to bother with making sure I have references to userdatas, or
write multiple __index and __newindex metamethods and what not.  The data is
converted to native Lua types upon inport, and is coverted from native Lua
types upon export, and for what we use it for, it's fast enough to not worry
about the speed of conversion.

  -spc