玩了一会儿之后,我想到了这个:
首先,向 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 设置为包含我们所有函数的表(有关这两个选项的更多信息,请参阅此内容)。我更喜欢后者,因为它使我们免于循环遍历所有函数、进行讨厌的字符串比较,并且我们可以做一些复制粘贴并重用 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 表创建一个元表,并为您创建的包含它们的对象创建另一个元表。