How to store a value type in a userdata?
Asked Answered
C

1

1

This SO article is the same thing, but the answer is unhelpful because the answer was in Lua and the question was about the C-API. So I'm asking again. Hopefully, others will benefit from this question.

I'm actually having 2 problems (I can't get y an z to work, and I can't get helloworld() to work)

I'm trying to get to this:

local x = MyCBoundLib.GetSomething()
print(x.y)
print(x.z)

Where x is a userdata. I keep getting attempt to index a userdata value

I know that "userdata isn't indexable without a metatable because it's C/C++ data"

In my C-code, I do something like this to try and wrap the object.

int push_Something(lua_State *L, void *object)
{
    struct SomethingWrapper *w = (struct SomethingWrapper *)lua_newuserdata(L, sizeof(struct SomethingWrapper));
    w->object = object;

    luaL_setmetatable(L, "Something");
    return 1;
}

Earlier, I tried to register a metatable called Something, like so:

luaL_newmetatable(L, "Something");
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
luaL_setfuncs(L, some_funcs, 0);
lua_pop(L, 1);

Where some_funcs has:

static luaL_Reg const some_funcs [] =
{
    { "helloworld",     l_helloworld },
    { NULL, NULL }
};

If I try print(x.helloworld()), I get the same error: attempt to index a userdata value

In none of my code do I know how to properly attach the value type y or z.

Changchun answered 30/4, 2015 at 1:53 Comment(2)
You registered/created the metatable before using push_Something correct?Bartlett
Correct, the metatable was created firstChangchun
C
4

First, for helloworld your code works:

/* file: hw.c
 * on Debian/Ubuntu compile with:
 *  `gcc -I/usr/include/lua5.2 -fpic -shared -o hw.so hw.c`
 */
#include <lua.h>
#include <lauxlib.h>

struct SomethingWrapper {
  void *object;
};

static int l_helloworld(lua_State *L) {
  lua_pushliteral(L, "Hello World!");
  return 1;
}

static luaL_Reg const some_funcs[] = {
  { "helloworld", l_helloworld },
  { NULL, NULL }
};

int push_Something(lua_State *L, void *object) {
  struct SomethingWrapper *w = lua_newuserdata(L, sizeof(*w));
  w->object = object;
  luaL_setmetatable(L, "Something");
  return 1;
}

int luaopen_hw(lua_State *L) {
  luaL_newmetatable(L, "Something");
  lua_pushvalue(L, -1);
  lua_setfield(L, -2, "__index");
  luaL_setfuncs(L, some_funcs, 0);
  lua_pop(L, 1);

  push_Something(L, NULL);
  return 1;
}

and the test script:

-- file: hwtest.lua
local x = require( "hw" )
print( x.helloworld() )

Output is:

Hello World!

For accessing properties on a userdata you need to set __index to a function instead of a table. The function is called with two arguments (the userdata, and the key) whenever you try to access a field on the userdata, and you can query your C object and push the desired result.

It gets a little more complicated if you intend to support methods and properties at the same time, but the basic approach is the following: You use a function as __index metamethod. This function has access to a table of methods (e.g. via an upvalue or the registry, etc.) and tries to lookup the given key in that table. If you succeed you return that value. If you come up with nothing, you compare the given key to the valid property names of your C object and return the respective values if you get a match. (If you don't get a match you can return nil or raise an error -- that's up to you.)

Here is a reusable implementation of that approach (extracted from the moon toolkit):

static int moon_dispatch( lua_State* L ) {
  lua_CFunction pindex;
  /* try method table first */
  lua_pushvalue( L, 2 ); /* duplicate key */
  lua_rawget( L, lua_upvalueindex( 1 ) );
  if( !lua_isnil( L, -1 ) )
    return 1;
  lua_pop( L, 1 );
  pindex = lua_tocfunction( L, lua_upvalueindex( 2 ) );
  return pindex( L );
}

MOON_API void moon_propindex( lua_State* L, luaL_Reg const methods[],
                              lua_CFunction pindex, int nups ) {
  if( methods != NULL ) {
    luaL_checkstack( L, nups+2, "not enough stack space available" );
    lua_newtable( L );
    for( ; methods->func; ++methods ) {
      int i = 0;
      for( i = 0; i < nups; ++i )
        lua_pushvalue( L, -nups-1 );
      lua_pushcclosure( L, methods->func, nups );
      lua_setfield( L, -2, methods->name );
    }
    if( pindex ) {
      lua_pushcfunction( L, pindex );
      if( nups > 0 ) {
        lua_insert( L, -nups-2 );
        lua_insert( L, -nups-2 );
      }
      lua_pushcclosure( L, moon_dispatch, 2+nups );
    } else if( nups > 0 ) {
      lua_replace( L, -nups-1 );
      lua_pop( L, nups-1 );
    }
  } else if( pindex ) {
    lua_pushcclosure( L, pindex, nups );
  } else {
    lua_pop( L, nups );
    lua_pushnil( L );
  }
}

Usage:

/*  [ -nup, +1, e ]  */
void moon_propindex( lua_State* L,
                     luaL_Reg const* methods,
                     lua_CFunction index,
                     int nup );

This function is used for creating an __index metafield. If index is NULL but methods is not, a table containing all the functions in methods is created and pushed to the top of the stack. If index is not NULL, but methods is, the index function pointer is simply pushed to the top of the stack. In case both are non NULL, a new C closure is created and pushed to the stack, which first tries to lookup a key in the methods table, and if unsuccessful then calls the original index function. If both are NULL, nil is pushed to the stack. If nup is non-zero, the given number of upvalues is popped from the top of the stack and made available to all registered functions. (In case index and methods are not NULL, the index function receives two additional upvalues at indices 1 and 2.) This function is used in the implementation of moon_defobject, but maybe it is useful to you independently.

If you try to store arbitrary Lua values in a userdata (like the title suggests) -- you can't. But you can associate an extra table (called "uservalue") with each userdata and store arbitrary values there. The approach is similar to the one above, but instead of matching with predefined property names and accessing the C object directly you first push the uservalue table (using lua_getuservalue) and then lookup your field there.

Colp answered 30/4, 2015 at 12:21 Comment(3)
An __index table on a userdata should work just fine it just won't allow access to any C-side properties by itself. I'm glad you said the original code worked it looked fine to me so I was confused.Bartlett
Yes, but since the OP uses luaL_newmetatable/luaL_setmetatable, the metatable and the __index metamethod (function or table) are shared by all objects of this particular userdata type. If you want to implement properties using an __index table, you need separate metatables for every userdata object.Colp
Agreed. I was just responding to "For accessing properties on a userdata you need to set __index to a function instead of a table." which made it sound (to me at least) that a table just will not work.Bartlett

© 2022 - 2024 — McMap. All rights reserved.