【问题标题】:How to extract C++ object pointer from Lua如何从 Lua 中提取 C++ 对象指针
【发布时间】:2013-08-18 04:27:32
【问题描述】:

我在 C++ 中有一个名为“Point”的类:

class Point
{
public:
  int x, y;

  //constructor
  Point(int x, int y)
  {
    this->x = x;
    this->y = y;
  }
};

我的目标是能够用 Lua 脚本实例化一个 Point 对象,并从 Lua 堆栈中提取指向该对象的指针。

这是我(目前不工作)的尝试,希望能澄清我到底想要做什么;请注意,此代码本质上是从 this tutorial 修改的复制/粘贴,并且我使用的是 Lua 5.2:

static int newPoint(lua_State *L) 
{
    int n = lua_gettop(L);
    if (n != 2) 
       return luaL_error(L, "expected 2 args for Point.new()", n); 

    // Allocate memory for a pointer to object
    Point **p = (Point **)lua_newuserdata(L, sizeof(Point *));  

    double x = luaL_checknumber (L, 1);      
    double y = luaL_checknumber (L, 2); 

    //I want to access this pointer in C++ outside this function
    *p = new Point(x, y);

    luaL_getmetatable(L, "Point"); // Use global table 'Point' as metatable
    lua_setmetatable(L, -2); 

  return 1; 
}

static const luaL_Reg pointFuncs[] = {
  {"new", newPoint},
  {NULL, NULL}
};

//register Point to Lua
void registerPoint(lua_State *L)
{
    lua_createtable(L, 0, 0);
    // Register metatable for user data in registry
    luaL_newmetatable(L, "Point");
    luaL_setfuncs(L, pointFuncs, 0);           
    lua_pushvalue(L,-1);
    lua_setfield(L,-2, "__index");
    lua_setglobal(L, "Point");
}

Point* checkPoint(lua_State* L, int index)
{
  void* ud = 0;
  luaL_checktype(L, index, LUA_TTABLE); 
  lua_getfield(L, index, "__index");
  ud = luaL_checkudata(L, index, "Point");;  

  return *((Point**)ud);     
 }

 int main()
 {
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);
        registerPoint(L);
    luaL_dofile(L, "testz.lua");
    lua_getglobal(L, "Point");
    Point *foo = checkPoint(L, lua_gettop(L));
        std::cout << "x: " << foo->x << " y: " << foo->y;
    lua_close(L);
    return 0;
 }

这是 Lua 脚本:

local point = Point.new(10,20)

运行此代码,我在该行收到以下错误:“bad argument #3 (Point expected, got table)”: ud = luaL_checkudata(L, index, "Point") 在 checkPoint() 函数中。

如果有人能引导我朝着正确的方向前进,将不胜感激。

【问题讨论】:

    标签: c++ lua


    【解决方案1】:

    我建议不要编写自己的绑定,而是使用经过测试和记录的绑定,例如 luabridgeluabind

    您的绑定将简化为:

    getGlobalNamespace (L)
        .beginClass<Point>("Point")
         .addConstructor<void (*) (int,int)>()
         .addData("X", &Point::x)
         .addData("Y", &Point::y)
        .endClass()
    ;
    

    你的 lua 看起来像

    local point = Point(10,20)
    

    使用 luabridge。

    对于提取值,请参阅例如 LuaRef in luabridge。如果你使用 c++11,还有其他一些非常漂亮的 lua 绑定库。

    【讨论】:

    • 我一定会去看看 luabind。但是,作为一种学习经验,我确实希望能够实现这一目标。不幸的是,即使在我的 Lua 代码中切换到冒号运算符并更改了 newPoint() 函数中的参数检查后,我仍然遇到与以前相同的错误。你知道这是为什么吗?
    • 更新了我的答案,因为 id 没有实际解释,greatwolf 确实解释了
    【解决方案2】:

    您的上述用法和绑定存在几个问题。

    在您的测试脚本中,local point 不会被您的主机 c++ 程序看到。这是因为,嗯,它是该脚本的本地文件。如果您希望从 C++ 中访问它,要么将 point 作为脚本的值返回,要么将其设为全局并使用 lua_getglobal(L, "point") 检索它。

    checkPoint 函数有一些不必要的代码。如果您查看 Lua C API 提供的其他 luaL_check* 函数,它们主要检查堆栈索引处的给定值是否是正确的类型。如果是,则将该类型转换为 C++ 可以使用的类型。

    因此,在您的“检查点”功能中,您真正需要做的就是:

    Point* checkPoint(lua_State* L, int index)
    {
      return *((Point **) luaL_checkudata(L, index, "Point"));
    }
    

    如果你把你的脚本改成这样,例如:

    local point = Point.new(10,20)
    return point
    

    您应该能够通过以下方式从 C++ 访问 point

    // ...
    luaL_dofile(L, "testz.lua");
    
    Point *foo = checkPoint(L, -1);
    std::cout << "x: " << foo->x << " y: " << foo->y;
    // ...
    

    另一个重要的一点是关于暴露给 lua 的 C++ 对象的生命周期。因为每当从脚本中调用 Point.new 时,您都在使用 C++ 'new' 运算符进行分配和构造,所以您间接地说 让 lua 处理这个暴露的 C++ 对象的生命周期。这意味着您还需要实现__gc 元方法来充当“终结器”或非确定性析构函数。如果没有这个,你将无法“删除”point 对象并在 lua 垃圾收集相应的用户数据时回收空间。

    为此,您可以按如下方式扩充您的代码:

    • 编写一个 deletePoint 函数,由 lua 在 userdata gc 上调用。

      static int deletePoint(lua_State *L)
      {
        Pointer **p = checkPoint(L, 1);
        delete *p;
        *p = NULL;  // probably not needed but to be safe
        return 0;
      }
      
    • 将它添加到您的pointFuncs 以便 lua 知道它。

      static const luaL_Reg pointFuncs[] =
      {
        {"new", newPoint},
        {"__gc", deletePoint},
        {NULL, NULL}
      };
      

    【讨论】:

    • @user2687268 我添加了一条关于清理用户数据的说明。
    • 这是一个很好的解释,但它似乎假设垃圾收集器不会在点指针从 lua 状态获得点之前运行。这个简单的例子可能不是这种情况,但是在更复杂的脚本中,您将不知道 gc 何时会删除您的观点。因此,绑定库中的内存生命周期管理大惊小怪。
    • 附注典型的解决方案之一是将您的对象包装在一个引用计数的对象中,以便垃圾收集器调用仅在 c++ 端没有引用时释放内存,反之亦然,如果您在 c++ 端删除包装器对象,如果原始对象仍然作为用户数据存在,则应该由垃圾收集器释放内存。
    猜你喜欢
    • 2010-11-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-05
    • 2013-03-05
    • 2021-07-12
    相关资源
    最近更新 更多