【问题标题】:Lua userdata variable communication with C++Lua 用户数据变量与 C++ 的通信
【发布时间】:2016-08-23 10:56:37
【问题描述】:

我有一个程序,用户可以使用 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;
}

由于向用户显示了框架,用户只需单击操作系统提供的关闭按钮即可关闭框架。这会产生一个关闭事件,它的处理方式如下:

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 完全不同的地址,因此我不知道我指的是哪个类型的框架变量。

据我了解,wb2 有不同的内存地址可能是由于 3 种情况:

1) frm 是完整的用户数据

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

3) 我的思维方式完全错误,或者看不到简单的东西。

【问题讨论】:

    标签: c++ lua


    【解决方案1】:

    现在据我所知,我需要做的就是在全局表中搜索用户数据类型 Frame 并比较内存地址,以便我知道我选择了正确的帧,然后将其设置为 nil。

    你的理解是错误的。

    首先,您没有将用户数据返回给 Lua。您返回了 light 用户数据。那不一样。轻用户数据的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 一个值,它现在 拥有那个值。它可以为所欲为,违反合同通常被认为是不礼貌的。

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

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

    struct FramePtr
    {
        Frame *ptr;
    };
    

    Lua 将传递FramePtr 的单个分配。因此,如果您将该分配的FramePtr 设置为NULL,每个人都会看到它。没有遍历全局表或类似的东西。

    当然,从FramePtr 访问Frame 需要额外的间接寻址。但是,通过使用完整的用户数据而不是轻量用户数据,您可以将适当的元表附加到它(轻量用户数据不会获得每个对象的元表;每个轻量用户数据共享相同的元表)。 p>

    【讨论】:

    • 感谢您的 cmets 和搜索全局表的示例。我现在明白为什么不喜欢这样不是一个好主意。但是,我不确定我是否在 FrmPtr 的同一页面上。有没有可以看的例子?
    • 我遵循wxLua采用的原理,比如不破坏框架而是隐藏它。到目前为止它运行良好,但我仍然对在 Lua 中创建变量值nil 的方式感兴趣。 memset(&frm, 0, sizeof(frm)怎么样,你推荐用吗?
    • @macroland: "我还是对在 Lua 中将变量值设为 nil 的方式感兴趣" 你可以对它感兴趣;这不会改变你做不到的事实。总会有一些地方可以让 Lua 对你隐藏一个值。你想要memsetfrm 是什么?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-07-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-08
    • 2013-02-08
    • 2021-12-11
    相关资源
    最近更新 更多