【问题标题】:lua/C++ binding of objects with static memberslua/C++ 对象与静态成员的绑定
【发布时间】:2012-08-26 02:58:42
【问题描述】:

我对如何将 C++ 类转换为 Lua 类非常感兴趣。而且我发现了一个很棒的助手类——LunaWrapper (Description here)

但似乎,在 Lua 中,类本身唯一持续存在的东西(这里不讨论结果对象!)只是构造函数。也就是说,如果有的话,我不能调用任何静态函数。 (例如,让我们在 LunaWrapper 页面上描述的示例中添加一个函数:

static bool Foo::bar(const char* text)
{
    printf("in bar\n");
}
)

所以,例如,我想在 Lua 中做什么:


local foo = Foo()
foo:foo()
Foo.bar() -- this should output "in bar", but this wont work! And this is what I want.
foo.bar() -- this should also output "in bar", but this is probably going to work.

我该怎么做?

【问题讨论】:

  • LunaWrapper 不这样做。所以你必须自己做。此外,Lua 是一个专有名称,而不是首字母缩略词。所以你不应该使用全部大写。
  • 确实,Lua 包装器往往对它们暴露的内容有点挑剔,人们通常只是自己编写以确保它们符合他们的需求,或者至少我过去就是这样做的。如果您想向 LunaWrapper 添加功能,请记住 Lua 类型是美化的表,而对象只是分配了这样一个元表,然后用于查找方法。所以在你的情况下 Foo 是一个元表。它是在静态 Luna::Register 函数中创建的。你可以在那里做任何你想做的事情,甚至可以在元表中添加一个元表来做 Foo.bar,而不仅仅是 foo.bar
  • 好的,看来我明白了。是否有可能将 Foo() 行为保留为构造函数?是应该使用一些元方法吗?是__call元方法吗?
  • 准确地说,Register 函数做了两件事:它创建一个名为 T::className 的元表,以及一个名为 T::className 的函数,它创建对象并将其元表设置为它产生了。所以是的 - 您可以保留构造函数的行为并只修改元表。

标签: c++ c binding static lua


【解决方案1】:

玩了一会儿,我想到了这个:

首先,向 Luna 添加另一个结构并在您的类中的另一个表中定义静态函数(为了匹配 LunaWrapper 代码的样式,您可以使用任何您喜欢的方式获取该信息:

struct StaticRegType {
  const char *name;
  int(*mfunc)(lua_State*); // pointers to static members are C-style func pointers
                           // and are incompatible with int(T::*mfunc)() ones
};
...
static const Luna<Foo>::StaticRegType StaticRegister[];
...
const Luna<Foo>::StaticRegType Foo::StaticRegister[] = {
   { "bar", &Foo::bar },
   { 0 }
};

现在是有趣的部分。不幸的是,事实证明你必须更改相当多的 LunaWrapper 才能让它做你想做的事。我们将创建一个名为 Foo 的表并使用 __call 方法附加一个元表,而不是创建一个名为 Foo 的函数来调用构造函数,以便我们维护 Foo() 构造函数语法(这在 Luna::Register 中) :

lua_newtable(L);
... // here we'll insert our manipulations of the table
lua_setglobal(L, T::className); // setglobal will pop the table off the stack
                                // so we'll do whatever we want to it and then give it
                                // a name to save ourselves the extra lookup

创建元表并添加构造函数和垃圾回收函数:

luaL_newmetatable(L, T::className);
lua_pushstring(L, "__gc");
lua_pushcfunction(L, &Luna<T>::gc_obj);
lua_settable(L, -3);
lua_pushstring(L, "__call");
lua_pushcfunction(L, &Luna<T>::constructor);
lua_settable(L, -3);

现在我们需要添加我们所有的方法。我们将使用 __index 元方法 - 两个选项:1. 将 __index 设置为一个 cfunction,它采用我们尝试从 lua 调用的名称并运行该函数,或者 2. 将 __index 设置为包含我们所有函数的表(有关这两个选项的更多信息,请参阅this)。我更喜欢后者,因为它使我们免于循环遍历所有函数、进行讨厌的字符串比较,并且我们可以做一些复制粘贴并重用 Luna 的闭包。然而,它确实需要我们创建一个新的staticThunk 函数来处理我们的新方法:

static int staticThunk(lua_State *L) {
  int i = (int)lua_tonumber(L, lua_upvalueindex(1));
  return (*(T::StaticRegister[i].mfunc))(L);
}

请注意,它相当简单,因为我们不需要获取我们正在调用函数的对象(我也很喜欢模板抽象,让编译器处理触发正确函数的细节对于我们的对象,但这只是 Luna 作者的一个很好的决定;))。

现在我们需要为 __index 创建一个表,并在其中添加方法。

lua_pushstring(L,"__index"));
lua_newtable(L);
// adding the normal methods is the same as in Luna (the for-loop over T::Register)
// add the static methods by going over our new StaticRegister array (remember to use
// staticThunk for them and thunk for the members
lua_settable(L, -3); // push the __index table to the metatable

差不多了...这是元表技巧 - 我们已经构建了 Foo 表(即使它还没有真正命名为 Foo),现在我们将设置我们为我们的对象构建的元表,因为它也是元表。这样我们就可以同时做到Foo.bar()local foo = Foo(); foo.bar()

lua_setmetatable(L, -2); // at this point the stack contains our metatable right under
                         // our table
lua_setglobal(L, T::className); // name the darn thing

您需要做的最后一件事是删除 Luna::constructor 中与实际构造对象无关的任何内容(额外的好处 - Register 实际上注册了对象类型,而构造函数实际上只是分配它,按照它的方式如果你问我应该是)。我们将原来在这里的循环移到了 Register 函数中。我们完成了!

注意:此解决方案的一个缺点是,虽然为方便起见,它允许您同时调用 Foo.bar()foo.bar(),但它还允许您调用 Foo.foo(),这没有意义,会被当作在编译等效的 C/C++ 程序期间非法尝试调用成员函数,但在 Lua 中会因运行时错误而失败。如果您想摆脱这种行为,您必须为不包含成员函数的 Foo 表创建一个元表,并为您创建的包含它们的对象创建另一个元表。

【讨论】:

  • 好吧,我在普通 Lua 中创建了一些我想要的不同的东西,稍后会发布它,但无论如何感谢您的调查!是的,我发现丢失的是“__call”方法和“基本”类表,所以我成功地创建了一个包含静态变量、静态函数(甚至是私有函数)以及公共和私有变量的类。稍后会发布解决方案。
猜你喜欢
  • 1970-01-01
  • 2014-07-11
  • 1970-01-01
  • 2022-01-22
  • 1970-01-01
  • 2010-12-13
  • 1970-01-01
  • 2016-03-06
  • 2014-02-17
相关资源
最近更新 更多