Lua userdata 变量与 C++ 的交互

我有一个程序,用户可以使用 Lua 命令创建 Frame,例如:

frm=Frame.new()

上述命令会向用户显示一个窗口。在幕后,C++ 包装器如下所示:

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

   return wb;
}

由于 Frame 被显示给用户,因此用户可以通过单击操作系统提供的关闭按钮来关闭 Frame。这会生成一个关闭事件,并且以下处理方式:

void Frm::OnClose(wxCloseEvent& evt)
{
    //为了简洁而省略
    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){ //此部分不起作用
             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 );
}

现在的目标是,当用户关闭 Frame 时,由 frm=Frame.new() 创建的变量应该为 nil,以便用户无法调用其中的方法,比如 frm:size(),这会使程序崩溃。

在上述处理关闭事件的 C++ 代码中,wb1 和当前 Frame 具有相同的内存地址。现在,根据我的理解,我所需要做的就是在全局表中搜索 userdata 类型的 Frame,然后比较内存地址,以便知道我选择的是正确的 Frame,然后将其设置为 nil。

然而,Frame* wb2=(Frame*)lua_touserdata(glbLuaState,-1); 返回的地址与 wb1 完全不同,因此我无法知道我正在引用哪个类型为 Frame 的变量。

根据我的理解,wb2 具有不同的内存地址,可能出现 3 种情况:

  1. frm 是 full userdata

  2. frm 在全局 lua 表中,因此具有不同的地址(尽管这对我来说没有意义,因为我在 C++ 中推送了 Frame 的地址)。

  3. 我完全错误地思考或者看不到简单的事情。

点赞
用户734069
用户734069

根据我的理解,我所需要做的就是在全局表中搜索userdata类型的Frame并将其内存地址进行比较,以便我知道我正在选择正确的帧,然后将其设置为nil

你的理解是错误的。

首先,你没有将userdata返回给Lua。你返回了轻量级userdata。这是不同的。轻量级userdatalua_typeLUA_TLIGHTUSERDATA

其次,即使你修复了这个问题,你也没有遍历全局表内的表。所以即使出现这样一个简单的情况也会让你感到困惑:

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

Lua代码应该能够将其数据存储在任何它希望的地方。如果它想在某个表中存储一些userdata,你有什么权利阻止?

第三,即使你递归地遍历过所有可全局访问的表(有防止无限循环的保护),也不能阻止这种情况发生:

local frm = Frame.new()

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

由于Lua有适当的词法作用域,GlobalFunc会在内部存储对frm局部的引用。由于frm是一个local变量,您无法从全局变量中迭代访问它。

一般来说,如果您将一个值传递给Lua,那么Lua现在拥有该值。它可以做任何它想做的事情,一般认为打破这个协议是不礼貌的。

虽然它并非不可能。处理它的方法是使用实际的userdata而不是轻量级的userdata。每个常规的userdata是一个对象,一个完整的内存分配。在该分配内部,您将存储Frame指针。当该Frame需要被销毁时,您所要做的就是将userdata内的Frame指针设置为NULL

从概念上讲,在C ++中,它是这样的:

struct FramePtr
{
    Frame *ptr;
};

Lua将传递一个FramePtr的单个分配。因此,如果将该分配的FramePtr设置为NULL,则所有人都会看到它。不需要遍历全局表等等。

当然,访问FramePtr中的Frame需要额外的间接引用。但是,通过使用完整的userdata而不是轻量级userdata,您还可以将适当的元表附加到其中(轻量级userdata不会获得每个对象的元表;每个轻量级userdata共享相同的元表)。

2016-08-23 14:24:56