关于lua:在C / c中实现__index元功能

implementing __index metafunction in C/c++

我有一个C回调/函数系统脚本,可以使用字符串和/或变体调用任何"已注册" C函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//REMOVED ERROR CHECKS AND ERRONEOUS STUFF FOR THIS POST
int LuaGameObject::LuaCallFunction( lua_State *luaState )
{
    if ( lua_isuserdata( luaState, 1 ) == 1 )
    {
        int nArgs = lua_gettop( luaState );

        //Get GameObject
        OGameObject* pGameObject = static_cast<OGameObject*>(lua_touserdata( luaState, 1 ));
        if ( pGameObject )
        {
            //Get FunctionName
            const char* functionNameString = lua_tostring( luaState, 2 );

            //Get Args
            std::vector<OVariant> args;
            for ( int i = 3; i <= nArgs; ++i )
            {
                OVariant variant;
                variant.SetFromLua( luaState, i );
                args.push_back( variant );
            }

            //Call it!
            CallGameObjectFunction( luaState, pGameObject, functionNameString, args );

            return 1;
        }
    }

    return 0;
}

OVariant LuaGameObject::ExecuteLua()
{
    lua_State *lState = luaL_newstate();

    luaL_openlibs( lState );
    lua_register( lState,"Call", LuaCallFunction );

    luaL_loadstring( lState, m_pScript );

    //now run it
    lua_pcall( lState, 0, 1, 0 );

    //process return values
    OVariant result;
    result.SetFromLua( lState, -1 );

    lua_close( lState );

    return result;
}

在lua中,我可以做这样的事情...

1
2
local king = Call("EmpireManager","GetKing")
Call("MapCamera","ZoomToActor",king)

但是,我觉得我可以使用__index元方法来简化lua ...

1
2
local king = EmpireManager:GetKing()
MapCamera:ZoomToActor(king)

我希望通过使用以下__index metamethod

的实现来实现简化的lua。

这是我注册__index元函数...的方式(主要是从在线示例中复制)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void LuaGameObject::Register( lua_State * l )
{
    luaL_Reg sRegs[] =
    {
        {"__index", &LuaGameObject::LuaCallFunction },
        { NULL, NULL }
    };

    luaL_newmetatable( l,"luaL_EmpireManager" );

    // Register the C functions into the metatable we just created.
    luaL_setfuncs( l, sRegs, 0 );
    lua_pushvalue( l, -1 );

    // Set the"__index" field of the metatable to point to itself
    // This pops the stack
    lua_setfield( l, -1,"__index" );

    // Now we use setglobal to officially expose the luaL_EmpireManager metatable
    // to Lua. And we use the name"EmpireManager".
    lua_setglobal( l,"EmpireManager" );
}

不幸的是,我似乎无法正确完成回调设置。 Lua正确调用了我的LuaGameObject :: LuaCallFunction,但是堆栈不包含我想要的内容。从LuaGameObject :: LuaCallFunction内,我可以在堆栈上找到函数名称和EmpireManager对象。但是,我在堆栈上找不到args。进行此设置的正确方法是什么?还是不可能?


绝对可以在Lua中将方法添加到userdata类型,如官方网站上的Lua编程指南中所述。

当您键入以下Lua代码时:

1
myUserdata:someMethod(arg1,arg2,arg3)

假定myUserdata是一个" userdata"对象,解释器将执行以下操作。

  • 调用getmetatable(myUserdata).__index(myUserdata,"someMethod")以获取someMethod的值。
  • 呼叫someMethod(myUserdata,arg1,arg2,arg3)someMethod可以是Lua可以调用的任何东西。示例:Lua或C函数,或带有__call元方法的表/用户数据。
  • 您的__index元方法应该只返回实现该方法的函数(或可从Lua调用的另一个对象)。像这样的东西:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // IMO, quite a misleading name for the __index metamethod (there is a __call metamethod)
    int LuaGameObject::LuaCallFunction( lua_State *l)
    {
        // todo: error checking
        OGameObject* pGameObject = static_cast<OGameObject*>(lua_touserdata( luaState, 1 ));
        std::string memberName = lua_tostring( luaState, 2 );

        int result = 1;
        if (memberName =="method1") {
            lua_pushcfunction(l,LuaGameObject::luaMethod1);
        } else if (memberName =="method2") {
            lua_pushcfunction(l,LuaGameObject::luaMethod2);
        } else {
            result = 0;
        }

        return result;
    }

    __index元方法返回的功能的基本框架:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    int LuaGameObject::luaMethod1(lua_State* l) {
        // todo: error checking.
        OGameObject* pGameObject = static_cast<OGameObject*>(lua_touserdata(l, 1));
        float arg1 = lua_tonumber(l, 2);
        // get other args
        pGameObject->method1(arg1 /*, more args if any.*/);
        // optionally push return values on the stack.
        return 0; // <-- number of return values.
    }

    我在从对象中调用方法时遇到了同样的问题,并使用了这篇文章来开发解决方案。

    我希望下面的示例对您有用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    #include <iostream>
    #include <string>
    #include <map>
    #include <functional>

    extern"C" {
    #include"lua/lua.h"
    #include"lua/lauxlib.h"
    #include"lua/lualib.h"
    }

    //template<class UserdataType>          // if will be work with lua garbage collector, use a function like that to delete the this_ptr (1st param)
    //int DeletePtr(lua_State *lua_state) { // It's necessary register the metatable.__gc and to trust in gc (create just pointer of LuaObjects
    //  UserdataType** this_ptr = reinterpret_cast<UserdataType**>(lua_touserdata(lua_state, 1));
    //  delete (*this_ptr);
    //  return 0;
    //}

    template<class UserdataType>
    int Closure(lua_State *lua_state) {
      UserdataType** ptr = reinterpret_cast<UserdataType**>(lua_touserdata(lua_state, 1)); // This closure is being called by call operator ()
      return (*ptr)->CallFunction(lua_state);                                              // To access the function name called use lua stack index with lua_upvalueindex(-1)
    }                                                                                      // Call the object method to resolve this called there

    template<class UserdataType>
    int ReturnClosure(lua_State *lua_state) {                // This function is called as a lookup of metatable.__index
      lua_pushcclosure(lua_state, Closure<UserdataType>, 1); // then we will return a closure to be called through call operator ()
      return 1;                                              // The 1st param (the only one) is the action name of function
    }                                                        // Then a closure will grant access to ReturnClosure params as upvalues (lua_upvalueindex)

    class LuaObject {
    public:
      LuaObject() :  userdata_name("userdata1") {
      }

      void CreateNewUserData(lua_State* lua_ptr, const std::string& global_name) {
        RegisterUserData(lua_ptr);
        LuaObject** this_ptr = reinterpret_cast<LuaObject**>(lua_newuserdata(lua_ptr, sizeof(LuaObject*)));
        *this_ptr = this;
        luaL_getmetatable(lua_ptr, userdata_name.c_str());
        lua_setmetatable(lua_ptr, -2);                        // setmetatable(this_ptr, userdata_name)
        lua_setglobal(lua_ptr, global_name.c_str());          // store to global scope
      }

      int CallFunction(lua_State* lua_state) const {
        std::string name = lua_tostring(lua_state, lua_upvalueindex(1)); // userdata:<function>(param2, param3)
        auto it = functions.find(name);                                  // <function> lua_tostring(lua_state, lua_upvalueindex(1))
        if (it != functions.end()) {                                     // <implicit this> lua_touserdata(l, 1)
          return it->second(lua_state);                                  // <param #1> lua_touserdata(l, 2)
        }                                                                // <param #2> lua_touserdata(l, 3)
        return 0;                                                        // <param #n> lua_touserdata(l, n+1)
      }

      void NewFunction(const std::string& name, std::function<int(lua_State*)> func) {
        functions[name] = func;
      }
    private:
      void RegisterUserData(lua_State* lua_ptr) {
        luaL_getmetatable(lua_ptr, userdata_name.c_str());
        if (lua_type(lua_ptr, -1) == LUA_TNIL) {
          /* create metatable for userdata_name */
          luaL_newmetatable(lua_ptr, userdata_name.c_str());
          lua_pushvalue(lua_ptr, -1); /* push metatable */
     
          /* metatable.__gc = DeletePtr<LuaObject> */
          //lua_pushcfunction(lua_ptr, DeletePtr<LuaObject>);
          //lua_setfield(lua_ptr, -2,"__gc");

          /* metatable.__index = ReturnClosure<LuaObject> */
          lua_pushcfunction(lua_ptr, ReturnClosure<LuaObject>);
          lua_setfield(lua_ptr, -2,"__index");
        }
      }
      std::map<std::string, std::function<int(lua_State*)>> functions;
      std::string userdata_name;
    };

    int main(int argc, char* argv[]) {
      lua_State* lua_state = luaL_newstate();
      luaL_openlibs(lua_state);
       
      LuaObject luaobj;
      luaobj.CreateNewUserData(lua_state,"test_obj");
      luaobj.NewFunction("action", [](lua_State* l)->int {
        std::string result ="action has been executed";
        LuaObject** ptr = reinterpret_cast<LuaObject**>(lua_touserdata(l, 1));
        result +="\
    #1 param is user_data (self == this) value ="
    + std::to_string(reinterpret_cast<size_t>(*ptr));    
        for (int i = 2; i <= lua_gettop(l); ++i) {
          result +="\
    #"
    + std::to_string(i)+" =" + lua_tostring(l, i);
        }
        result +="\
    #n param is passed on call operator () #n ="
    + std::to_string(lua_gettop(l));
        lua_pushfstring(l, result.c_str());
        return 1;
      });

      std::string lua_code;    
      lua_code +="print(test_obj:unknown_function()) \
    "
    ;  
      lua_code +="print(test_obj:action())           \
    "
    ;
      lua_code +="print(test_obj:action(1))          \
    "
    ;
      lua_code +="print(test_obj:action(1, 2))       \
    "
    ;
      lua_code +="print(test_obj:action(1, 2, 'abc'))\
    "
    ;

      if (!(luaL_loadbuffer(lua_state, lua_code.c_str(), lua_code.length(), NULL) == 0 && lua_pcall(lua_state, 0, LUA_MULTRET, 0) == 0)) {
          std::cerr <<"Lua Code Fail:" << lua_tostring(lua_state, -1) << std::endl;        
      }  
      lua_close(lua_state);
      return 0;
    }

    输出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    action has been executed
     #1 param is user_data (self == this) value = 13629232
     #n param is passed on call operator () #n = 1
    action has been executed
     #1 param is user_data (self == this) value = 13629232
     #2 = 1
     #n param is passed on call operator () #n = 2
    action has been executed
     #1 param is user_data (self == this) value = 13629232
     #2 = 1
     #3 = 2
     #n param is passed on call operator () #n = 3
    action has been executed
     #1 param is user_data (self == this) value = 13629232
     #2 = 1
     #3 = 2
     #4 = abc
     #n param is passed on call operator () #n = 4

    好吧,经过更多研究,我现在相信我不能使用__index元函数来调用带有参数的c函数。它仅将表名和键传递给回调。

    但是,对于感兴趣的任何人,它都可以用于类似表的对象,但不能用于函数(因为参数不会被压入堆栈)。我将其用于"属性"对象。它们没有参数,可以在lua中使用,如下所示。

    1
    2
    3
    local king = EmpireManager:king
    king:name ="Arthur"
    local name = king:name

    这些正确链接并调用适当的C对象。

    1
    2
    Actor::SetName(std::string name)
    std::string Actor::GetName()