macroland
macroland

Reputation: 1035

Lua userdata variable communication with C++

I have a program where users can create Frames using Lua command such as:

frm=Frame.new()

the above-mentioned command shows a frame to the user. Behind the scenes the C++ wrapper is as follows:

Frame* Frame_new(lua_State* L)
{
   int nargs=lua_gettop(L);
   Frame* wb=0;
   if(nargs==0){
       //Omitted
       wb=mainfrm->GetFrame();
       lua_pushlightuserdata(L,(void*)(wb));
       int key=luaL_ref(L, LUA_REGISTRYINDEX);
       wb->SetLuaRegistryKey(key);
   }

   return wb;
}

Since the frame is shown to the user, the user can close the frame by just clicking on the close button provided by the operating system. This generates a close event and it is handled as follows:

void Frm::OnClose(wxCloseEvent& evt)
{
    //Omitted for brevity
    int LuaRegistryKey=GetFrame()->GetLuaRegistryKey();
    lua_rawgeti(glbLuaState,LUA_REGISTRYINDEX,LuaRegistryKey);//userdata
    Frame* wb1=(Frame*)lua_touserdata(glbLuaState,-1); //userdata
    lua_pop(glbLuaState,1); //
    lua_getglobal(glbLuaState,"_G"); //table
    lua_pushnil(glbLuaState); //table key
    while (lua_next(glbLuaState,-2)) {//table key value
      const char* name = lua_tostring(glbLuaState,-2);//table
      if(lua_type(glbLuaState,-1)==LUA_TUSERDATA){
         Frame* wb2=(Frame*)lua_touserdata(glbLuaState,-1);
         if(wb2==m_Frame){ //this part doesnt work
             lua_pushnumber(glbLuaState,0);
             lua_setglobal(glbLuaState,name);
             lua_pop(glbLuaState,1);
             break;
         }
     }
     lua_pop(glbLuaState,1); //table key
   } //table
   lua_pop(glbLuaState,1); //
   if(m_Frame==wb1) {delete m_Frame; m_Frame=0; wb1=0;}
   if(wb1) {delete wb1; wb1=0;}
   luaL_unref(glbLuaState,LUA_REGISTRYINDEX,LuaRegistryKey );
}

Now the goal is when user closes the frame the variable created by frm=Frame.new() should be nil so that user can not call one of its methods, such as frm:size() which crashes the program.

In the above C++ code for handling the close event, wb1 and current frame has the same memory address. Now to my understanding all I need to do is search the global table for the userdata type Frame and compare the memory addresses so that I know I am choosing the right frame and then set it to nil.

However, Frame* wb2=(Frame*)lua_touserdata(glbLuaState,-1); returns a completely different address from wb1, therefore I cannot know which variable of type frame I am referring to.

To my understanding wb2 has a different memory address possibly due to 3 scenarios:

1) frm is a full userdata

2) frm is inside global lua table, therefore has a different address (although this doesnt make sense to me as I pushed the address of Frame in C++).

3) I am thinking completely in the wrong way or cant see something simple.

Upvotes: 1

Views: 465

Answers (1)

Nicol Bolas
Nicol Bolas

Reputation: 474276

Now to my understanding all I need to do is search the global table for the userdata type Frame and compare the memory addresses so that I know I am choosing the right frame and then set it to nil.

Your understanding is wrong.

First, you did not return userdata to Lua. You returned light userdata. That's different. The lua_type of light userdata is LUA_TLIGHTUSERDATA.

Second, even if you fixed that problem, you're not iterating through tables inside the global table. So something as simple as this would confound you:

global_var = {}
global_var.frame = Frame.new()

Lua code should be able to store its data wherever it wants. And if it wants to store some userdata in a table, who are you to say no?

Third, even if you iterated through every table accessible globally recursively (with protection from infinite loops), that wouldn't stop this:

local frm = Frame.new()

function GlobalFunc(...)
    frm:Stuff();
end

Because Lua has proper lexical scoping, GlobalFunc will store a reference to the frm local internally. And since frm is a local variable, you cannot get at it just from iterating through globals.

Generally speaking, if you give a value to Lua, it now owns that value. It can do whatever it wants, and it's generally considered rude to break this contract.

Though it's not impossible. The way to handle it is by using an actual userdata rather than light userdata. Each regular userdata is an object, a full allocation of memory. Inside that allocation you would store the Frame pointer. When it comes time for that Frame to be destroyed, all you have to do is set the Frame pointer inside the userdata to NULL.

Conceptually, it's like this in C++:

struct FramePtr
{
    Frame *ptr;
};

Lua would be passing around a single allocation of FramePtr. So if you set that allocation's FramePtr to NULL, everyone sees it. No iterating through global tables or somesuch.

Of course, accessing the Frame from a FramePtr requires an extra indirection. However, by using full userdata instead of light userdata, you can also attach a proper metatable to it (light userdata doesn't get per-object metatables; every light userdata shares the same metatable).

Upvotes: 1

Related Questions