即使表的字段已知,__index也总是会被调用的(Lua/C++)。

我想在 Lua 中声明全局元表,并由 C++ 应用程序注册。

我定义了元表和一些字段的 __index 变异方法,但是当 Lua 脚本访问已知字段时,总是调用我的 C++ 应用程序中的 __index。

例如,我想要注册一个名为 User 的全局表,其中包含三个字段:FirstName_、_LastName 和 _Age_。

下面是如何注册表的方式。该表封装在一个名为 TLuaStruct 的类中,使在 Lua 中注册表变得更加轻松。

bool TLuaStruct::InternalRegister( std::string tName, bool AddInGC )
{
    TLuaStack *ptStack;
    TLuaStruct **ptUserLuaStruct;

    ptStack = m_ptLua->GetStack();
    if( ptStack==NULL )
        return false;

    //  创建一个不会暴露给 Lua 脚本的元表。
    //  名称必须唯一
    //  调用后的堆栈:
    //  1,-1 | 表
    m_ptLua->NewMetaTable( m_tMetaName );

    //  注册所有属性
    PushProperties( false, false );

    //  注册分配给 __index 的回调
    ptStack->PushCFunction( TLuaStruct_Index_CallBack );

    m_ptLua->SetField( -2, "__index" );

    // 创建 UserData 以存储此地址。
    //  调用后的堆栈:
    //  2,-1 | userdata
    //  1,-2 | 表
    ptUserLuaStruct = ( TLuaStruct** )m_ptLua->NewUserData( sizeof( ptUserLuaStruct ) );
    *ptUserLuaStruct = this;
    m_pvLuaThisPtr = ( void* )ptUserLuaStruct;

    //  切换 userdata 和表
    //  调用后的堆栈:
    //  2,-1 | 表
    //  1,-2 | userdata
    ptStack->Insert( -2 );

    //  将元表与 userdata 关联
    //  调用后的堆栈:
    //  1,-1 | userdata
    m_ptLua->SetMetaTable( -2 );

m_ptLua->SetGlobal( tName.c_str() );

    return true;
}

如果我将元表本身分配给 __index 而不是一个 C 回调,那么脚本可以以只读方式访问 FirstName_、_LastName 和 _Age_(我想做的事情),但我无法知道脚本尝试访问未知字段的时间,因为我的 C++ 应用程序没有被调用。

也许 Lua 总是在元表是 userdata 时调用 __index,因此它确保值是最新的,但是如果定义只读变量(例如表中的函数),否则一直调用 __index 可以减慢应用程序的速度。

有没有人知道如何执行此操作? 谢谢。

点赞
用户1537403
用户1537403

该手册很好地解释了元表的工作原理,也许您误读了 - 元表中的任意字段没有影响,只有__index将“假”字段添加到对象中。

最好的解决方案是将所有静态值放在__index表中,然后为__index表创建_另一个_元表,并将_其___index字段设置为您的函数。

更新: 正如其他人提到的那样,存在一个稍微更紧凑的解决方案:将第一个元表的__index设置为它本身(并填充静态值),然后将_其_元表设置为另一个元表,该元表具有您的函数作为其__index

2013-12-02 17:48:01
用户1479549
用户1479549

解决方案:

Riv的解决方案几乎正确,但存在一些小差异,因此我发布了一个新答案。

解决方案是将主metatable的__index设置为自身,创建第二个metatable,其中__index被设置为C回调函数,并将此新metatable设置为主metatable而不是其__index。

bool TLuaStruct::InternalRegister(std::string tName, bool AddInGC)
{
    TLuaStack *ptStack;
    TLuaStruct **ptUserLuaStruct;

    ptStack = m_ptLua->GetStack();
    if (ptStack == NULL)
        return false;

    // 创建一个不会暴露给Lua脚本的metatable。
    // 名称必须唯一
    // 调用后的堆栈:
    // 1,-1 | table
    m_ptLua->NewMetaTable(m_tMetaName);

    // 注册所有常量成员
    PushProperties(false, false);

    // 复制metatable
    // 调用后的堆栈:
    // 2,-1 | table
    // 1,-2 | table
    ptStack->PushValue(-1);

    // 将其__index设置为自身,以便访问其所有常量成员
    // 调用后的堆栈:
    // 1,-1 | table
    m_ptLua->SetField(-2, "__index");

    // 创建另一个metatable,将用于非常量成员
    // 调用后的堆栈:
    // 2,-1 | table
    // 1,-2 | table
    m_ptLua->NewMetaTable("9999");

    // 推送在读取非常量成员时调用的C回调
    // 调用后的堆栈:
    // 3,-1 |函数
    // 2,-2 | table
    // 1,-3 | table
    ptStack->PushCFunction(TLuaStruct_Index_CallBack);

    // 设置metatable的__index
    // 调用后的堆栈:
    // 2,-1 | table
    // 1,-2 | table
    m_ptLua->SetField(-2, "__index");

    // 设置主metatable的metatable
    // 调用后的堆栈:
    // 1,-1 | table
    m_ptLua->SetMetaTable(-2);

    // 创建UserData来存储此处的地址。
    // 调用后的堆栈:
    // 2,-1 | userdata
    // 1,-2 | table
#warning check whether we must create a new pointer each time...
    ptUserLuaStruct = (TLuaStruct **)m_ptLua->NewUserData(sizeof(ptUserLuaStruct));
    *ptUserLuaStruct = this;
    m_pvLuaThisPtr = (void *)ptUserLuaStruct;

    // 切换userdata和table
    // 调用后的堆栈:
    // 2,-1 | table
    // 1,-2 | userdata
    ptStack->Insert(-2);

    // 设置userdata的metatable
    // 调用后的堆栈:
    // 1,-1 | userdata
    m_ptLua->SetMetaTable(-2);

    // 使用模块名称发布userdata对象。
    // 调用后的堆栈:
    // --空--
    m_ptLua->SetGlobal(tName.c_str());

    return true;
}
2013-12-05 09:12:26