Reputation: 1035
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
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