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

时间:2013-12-02 16:03:08

标签: c++ lua

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

我定义了metatable和某些字段的__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;

    //  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;
}

如果我将metatable本身分配给__index而不是C回调,脚本可以在readonly中访问 FirstName LastName Age (我想做什么)但我不知道脚本何时尝试访问未知字段导致我的C ++应用程序未被调用。

当metatable是userdata时,也许Lua总是调用__index,所以它确保值是最新的,但它可以很好地定义只读变量,如表中的函数,否则一直调用__index会慢在应用程序中。

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

2 个答案:

答案 0 :(得分:4)

本手册很好地解释了元表的工作原理,也许你误解了它 - 元表中的任意字段都没有效果,只有__index向对象添加“假”字段。

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

<强>更新 正如其他人所提到的,存在一个稍微紧凑的解决方案:将第一个metatable的__index设置为自身(并用静态值填充它),并将 metatable设置为另一个metatable,它有你的作为__index

答案 1 :(得分:3)

<强> SOLUTION:

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

解决方法是将主要元表的__index设置为自身,创建第二个元表,其中__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;

    //  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;
}