Lua/C++ :即使表的字段已知,也始终调用__index

Lua/C++ : __index is always invoked even the table's field is known

本文关键字:index 调用 字段 C++ Lua      更新时间:2023-10-16

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

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

例如,我想注册一个名为User的全局表,该表包含三个字段:FirstNameLastNamege

这是我登记桌子的方法。该表被封装在一个名为TLuaStruct的类中,这样可以更容易地在Lua中注册表。

bool TLuaStruct::InternalRegister( std::string tName, bool AddInGC )
{
    TLuaStack *ptStack;
    TLuaStruct **ptUserLuaStruct;
    ptStack = m_ptLua->GetStack();
    if( ptStack==NULL )
        return false;
    //  Create a metatable that won't be exposed to Lua scripts.
    //  The name must be unique
    //  Stack after the call:
    //  1,-1 | table
    m_ptLua->NewMetaTable( m_tMetaName );
    //  Register all of the properties
    PushProperties( false, false );
    //  Register the callback assigned to __index
    ptStack->PushCFunction( TLuaStruct_Index_CallBack );
    m_ptLua->SetField( -2, "__index" );
    // Create a UserData to store the address of this.
    //  Stack after the call:
    //  2,-1 | userdata
    //  1,-2 | table
    ptUserLuaStruct = ( TLuaStruct** )m_ptLua->NewUserData( sizeof( ptUserLuaStruct ) );
    *ptUserLuaStruct = this;
    m_pvLuaThisPtr = ( void* )ptUserLuaStruct;
    //  Switch the userdata and the table
    //  Stack after the call:
    //  2,-1 | table
    //  1,-2 | userdata
    ptStack->Insert( -2 );
    //  Associate the metatable to the userdata
    //  Stack after the call:
    //  1,-1 | userdata
    m_ptLua->SetMetaTable( -2 );
m_ptLua->SetGlobal( tName.c_str() );
    return true;
}

如果我将元表本身分配给__index,而不是C回调,脚本可以只读访问FirstNameLastNamege(我想做什么),但我不知道脚本何时尝试访问未知字段,因为我的C++应用程序没有被调用。

当元表是userdata时,Lua可能总是调用__index,这样它可以确保值是最新的,但最好在表中定义只读变量,比如函数,否则一直调用__index会减慢应用程序的速度。

有人知道怎么表演吗?谢谢

手册很好地解释了元表是如何工作的,也许你误解了它——元表中的任意字段没有效果,只有__index向对象添加了"假"字段。

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

更新:正如其他人所提到的,存在一个稍微紧凑的解决方案:将第一个元表的__index设置为它自己(并用静态值填充它),并将元表设置为另一个元表,它将您的函数作为它的__index

解决方案:

Riv的解决方案几乎是对的,但有一点不同,所以我发布了一个新的答案。

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

bool TLuaStruct::InternalRegister( std::string tName, bool AddInGC )
{
    TLuaStack *ptStack;
    TLuaStruct **ptUserLuaStruct;
    ptStack = m_ptLua->GetStack();
    if( ptStack==NULL )
        return false;
    //  Create a metatable that won't be exposed to Lua scripts.
    //  The name must be unique
    //  Stack after the call:
    //  1,-1 | table
    m_ptLua->NewMetaTable( m_tMetaName );
    //  Register all of the const members 
    PushProperties( false, false );
    //  Duplicate the metatable
    //  Stack after the call:
    //  2,-1 | table
    //  1,-2 | table
    ptStack->PushValue( -1 );
    //  Set its __index to itself so it can access all of its const members
    //  Stack after the call:
    //  1,-1 | table
    m_ptLua->SetField( -2, "__index" );

    //  Create another metable that will be used for non-const members
    //  Stack after the call:
    //  2,-1 | table
    //  1,-2 | table
    m_ptLua->NewMetaTable( "9999" );
    //  Push the C call back called when a non-const member is read
    //  Stack after the call:
    //  3,-1 | function
    //  2,-2 | table
    //  1,-3 | table
    ptStack->PushCFunction( TLuaStruct_Index_CallBack );
    //  Set the __index of the metable
    //  Stack after the call:
    //  2,-1 | table
    //  1,-2 | table
    m_ptLua->SetField( -2, "__index" );

    //  Set the metatable of the main metatable
    //  Stack after the call:
    //  1,-1 | table
    m_ptLua->SetMetaTable( -2 );
    // Create a UserData to store the address of this.
    //  Stack after the call:
    //  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;
    //  Switch the userdata and the table
    //  Stack after the call:
    //  2,-1 | table
    //  1,-2 | userdata
    ptStack->Insert( -2 );
    //  Set the metatable of the userdata
    //  Stack after the call:
    //  1,-1 | userdata
    m_ptLua->SetMetaTable( -2 );
    //  Publish the userdata object with the name of the module.
    //  Stack after the call:
    //  -- empty --
    m_ptLua->SetGlobal( tName.c_str() );
    return true;
}