2
votes

Assume I have a pointer owned from the C-side, that I wish to push onto the Lua stack in the form of a userdata. What would be the best way to accomplish this, while not sacrificing the ability to specify a specific metatable?

My original idea was to use a light userdata, but from what I've read here, all light userdatas share a common metatable. This isn't desirable, as it would force common behavior for all of my c-sided objects.

Considering I desire to reuse metatables for Lua allocated objects, simply passing a different this pointer to the metamethods, my second idea involved attaching an artificial this pointer to userdata objects.

struct ud
{               
    struct T* ptr;
    struct T data;
};

struct ud* p = (struct ud*)lua_newuserdata(L, sizeof(struct ud));
p->ptr = &(p->data);

Or, in the case of pushing a light userdata

struct T object;
struct T** p = (struct T**)lua_newuserdata(L, sizeof(struct T*));
*p = &object;

Is attaching a this pointer recommended, or is there a more API friendly alternative? My ultimate goal is simply to push an existing pointer to Lua, and associate a metatable with it in such a way that the metatable can be reused for both heavy and light userdatas.

1
You can't do pointer arithmetic with void*s. So what exactly do you want your first code example to do?Nicol Bolas
That was a mistake on my part. I forgot that pointer arithmetic is non-standard on void*. The intention was to attach a this pointer to the userdata, followed by the structure itself. Then, set the this pointer to the location just after itself, where I expect the structure to be allocated. I'll edit my code to reflect that in a standard way.TheCodeBroski

1 Answers

3
votes

A common (but not the only) way to do this is with a technique called a "boxed pointer". The idea is that you allocate a userdata large enough to hold the pointer and then attach your metatable etc to it.

void push_pointer(lua_State *L, void *p) {
    void **bp = lua_newuserdata(L, sizeof(p));
    *bp = p;

    // setup metatable, etc
}

Then a function called through the metatable will receive this userdata on the stack, and you unpack the original pointer from it.

// called via metatable
int some_function(lua_State *L) {
    assert(lua_type(L, 1) == LUA_TUSERDATA);
    void **bp = lua_touserdata(L, 1);
    void *p = *bp;

    // do stuff with p
}

There's a couple of things you have to think about when doing this:

  • Object lifetimes. If the C-side object is deallocated while something on the Lua side has a reference to its userdate, then you'll have a dangling pointer. If this is something that can happen then you need a way to deal with it. I dealt with this myself by having a registry of "live" C object that I can check against before trying to use the pointer.

  • Userdata equality. Since you push a new userdata for each pointer, two userdata with the same pointer won't compare equal in Lua. If that's important to you there's a few ways you can deal with it. A __eq metamethod is the easiest, but isn't always suitable. I've used the same object registry mentioned above to provide a pointer->userdata mapping and then, when pushing, if I already have a userdata for the pointer I just push it instead of creating a new one.