Lua userdata变量与c++的通信

Lua userdata variable communication with C++

本文关键字:通信 c++ userdata 变量 Lua      更新时间:2023-10-16

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

frm=Frame.new()
上面的命令

向用户显示一个帧。在幕后,c++包装器如下所示:

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;
}

由于帧是显示给用户的,因此用户只需点击操作系统提供的关闭按钮即可关闭该帧。这会生成一个close事件,处理方法如下:

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 );
}

现在的目标是当用户关闭帧时,由frm=Frame.new()创建的变量应该为nil,这样用户就不能调用它的一个方法,比如frm:size(),它会使程序崩溃。

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

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

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

1) frm是完整的userdata

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

我的思路完全错了,或者我看不清一些简单的东西。

现在我的理解是,我需要做的是搜索全局表的userdata类型帧和比较内存地址,以便我知道我选择了正确的帧,然后将其设置为nil。

你的理解是错误的。

首先,您没有向Lua返回userdata。您返回light userdata。这是不同的。轻userdata的lua_typeLUA_TLIGHTUSERDATA

其次,即使解决了这个问题,也不能在全局表中遍历表。所以像这样简单的东西会让你感到困惑:
global_var = {}
global_var.frame = Frame.new()

Lua代码应该能够在任何它想要的地方存储它的数据。如果它想在表中存储一些用户数据,你又有什么资格说不呢?

第三,即使你递归地遍历全局可访问的所有表(有无限循环保护),也不会阻止:
local frm = Frame.new()
function GlobalFunc(...)
    frm:Stuff();
end

由于Lua具有适当的词法作用域,GlobalFunc将在内部

存储对frm本地的引用。因为frm是一个local变量,你不能仅仅通过迭代全局变量来得到它。 一般来说,如果你给Lua一个值,它现在拥有这个值。它可以做任何它想做的事,通常认为破坏这个契约是不礼貌的。

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

从概念上讲,在c++中是这样的:
struct FramePtr
{
    Frame *ptr;
};

Lua将传递单个FramePtr分配。如果你设置那个分配的FramePtr为NULL,每个人都能看到它。没有遍历全局表之类的

当然,从FramePtr访问Frame需要额外的间接。然而,通过使用完整userdata而不是轻量级userdata,你也可以为它附加一个适当的元表(轻量级userdata不获得每个对象的元表;