从 C 导出类时 Lua 的方法和属性

我使用lua作为我的3D引擎的脚本语言。我有几个对象的lua“类”,现在我想使用属性而不是getter和setter。因此,而不是像这样:

local oldState = ui:GetChild("Panel1"):GetVisible()
ui:GetChild("Panel1"):SetVisible(not oldState)

我会这样做:

ui.Panel1.visible = not ui.Panel1.visible

问题是我的C++代码用于创建metatable和覆盖__index方法。这里是它,顺便说一下:

1.创建metatable:

void CLUAScript::RegisterClass(const luaL_Reg funcs[], std::string const& className)
{
   luaL_newmetatable(m_lua_state, std::string("Classes." + className).c_str());
   luaL_newlib( m_lua_state, funcs);
   lua_setglobal(m_lua_state, className.c_str());
}

2.实例化类(lua对象仅保存指向存储在C++代码中的实际数据的指针):

int CLUAScript::NewInstanceClass(void* instance, std::string const& className)
{
   if (!instance)
   {
       lua_pushnil(m_lua_state);
       return 1;
   }

   luaL_checktype(m_lua_state, 1, LUA_TTABLE);

   lua_newtable(m_lua_state);

   lua_pushvalue(m_lua_state,1);
   lua_setmetatable(m_lua_state, -2);

   lua_pushvalue(m_lua_state,1);
   lua_setfield(m_lua_state, 1, "__index");

   void **s = (void **)lua_newuserdata(m_lua_state, sizeof(void *));

   *s = instance;
   luaL_getmetatable(m_lua_state, std::string("Classes." + className).c_str());
   lua_setmetatable(m_lua_state, -2);
   lua_setfield(m_lua_state, -2, "__self");

   return 1;
}

问题是如何同时具有方法和属性。如果我只将__index添加到CLUAScript::RegisterClass函数数组中,它将永远不会被调用。我无法想象在CLUAScript::NewInstanceClass中删除其重新定义的方法。

如果此代码不足够,请参阅与lua一起使用的文件的链接: LUA辅助类, UI函数, 对象功能,和 测试lua脚本

点赞
用户501459
用户501459

问题在于如何同时拥有方法和属性。

广义来讲,方法只是恰好转化为函数的属性。

如果我只是将 __index 添加到 RegisterClass 函数数组中,它就永远不会被调用。

这才是实际问题,对于你的问题来说,文章其余部分只是分散注意力。

根据文档,luaL_newlib 会创建一个新表。luaL_newmetatable 也是如此。在 RegisterClass 中你创建了两个表,这是没有意义的。你需要仅创建元表,而且就是这个元表需要添加 __index 和 __newindex 元方法。

如果你希望手动转换数据来回到你的 C++ 类实例属性,你不会让 __index 只是指向一个函数表(实现类方法的快捷方式)。访问时你必须写出一个函数来区分方法(值来自类范围)和属性(值来自实例范围)。


以下是如何在 Lua 中访问方法/属性的例子。虽然使用了 C API,但方法是相同的:

-- 这是你在 RegisterClass 中为 C++ 类 'Foo' 创建的元表
Foo = { }

-- 下面是你的 __index 元方法在 C++ 代码中的具体实现,但你需写出代码来区分 'key' 对应 C++ 对象中的哪些属性,然后将其推到栈上。
function Foo.__index(instance,key)
    local method = rawget(Foo,key)
    if method then
        return method
    end
    return instance.properties[key]
end

-- 下面是赋值元方法的实现,不过如果你希望用户能够向 Lua 对象中添加属性以兼容 C++ 对象属性,则还需要在正确的位置写入值。
function Foo.__newindex(instance,key,value)
    instance.properties[key] = value
end

-- 这不必是元表上的方法
function Foo:new(state)
    return setmetatable({ properties = state}, self)
end

-- 一个类方法的例子
function Foo:dump()
    print('dump:', self.x, self.y)
end

-- 模拟用户数据,一个 C++ 类实例
cppClassInstance = {
    x = 10,
    y = 20,
}

obj = Foo:new(cppClassInstance)
print(obj.x) -- 读取 `x`,它被解析为 cppClassInstance.x
obj.x = 5150 -- 写入 'x',写入 cppClassInstance.x
print(obj.x) -- 验证更改
obj:dump() -- 调用一个类方法
2015-01-28 23:14:36