【问题标题】:implementing __index metafunction in C/c++在 C/C++ 中实现 __index 元函数
【发布时间】:2018-11-25 14:37:21
【问题描述】:

我有一个 C++ 回调/仿函数系统的脚本,它可以使用字符串和/或变体调用任何“注册的”C++ 函数。

//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 中我可以做这样的事情......

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

不过,我感觉可以使用__index元方法来简化lua...

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

我希望通过使用__index元方法的以下实现来实现简化的lua

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

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 对象。但是,我在堆栈上找不到参数。设置它的正确方法是什么?还是不可能?

【问题讨论】:

  • 您的部分问题是范围解析运算符 :: 不是 C 语言的一部分,也没有 C/C++ 语言。我建议只切换到 C++ 语言而不用担心 C。
  • 我想你想做的是在 Lua 脚本中使用冒号语法:MapCamera:ZoomToActor(king)。见:stackoverflow.com/questions/4911186/…
  • tx 伙计们快速响应!我正在使用 C++。我已经编辑了帖子以减少混乱。请记住,当我的 LuaGameObject::LuaCallFunction 到达本地 king = EmpireManager.GetKing() 时,它会从 lua 调用。但我不确定如何检索参数。 (也许不可能?)

标签: c++ lua callback meta-method


【解决方案1】:

绝对可以在 Lua 中为 userdata 类型添加方法,如官方网站的 Programming in Lua 指南中所述。

当你键入以下 Lua 代码时:

myUserdata:someMethod(arg1,arg2,arg3)

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

  1. 调用getmetatable(myUserdata).__index(myUserdata,"someMethod")获取someMethod的值。
  2. 致电someMethod(myUserdata,arg1,arg2,arg3)。 someMethod 可以是任何可以从 Lua 调用的东西。示例:Lua 或 C 函数,或带有 __call 元方法的表/用户数据。

您的__index 元方法应该只返回一个实现该方法的函数(或另一个可从 Lua 调用的对象)。像这样的:

// 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 元方法返回的函数的基本框架:

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.
}

【讨论】:

    【解决方案2】:

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

    但是,对于任何感兴趣的人,它可以用于类似表格的对象,但不能用于函数(因为参数不会被压入堆栈)。我将它用于我的“财产”对象。它们没有参数,可以在lua中使用如下...

    local king = EmpireManager:king
    king:name = "Arthur"
    local name = king:name
    

    这些正确链接到并调用适当的 C++ objects.functions

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

    【讨论】:

      【解决方案3】:

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

      希望下面的例子对你有用。

      #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 += "\n #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 += "\n #" + std::to_string(i)+ " = " + lua_tostring(l, i);
          }
          result += "\n #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()) \n";  
        lua_code += "print(test_obj:action())           \n";
        lua_code += "print(test_obj:action(1))          \n";
        lua_code += "print(test_obj:action(1, 2))       \n";
        lua_code += "print(test_obj:action(1, 2, 'abc'))\n";
      
        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;
      }
      

      输出:

      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
      

      【讨论】:

        猜你喜欢
        • 2011-11-24
        • 1970-01-01
        • 1970-01-01
        • 2013-10-18
        • 2011-10-27
        • 1970-01-01
        • 2023-01-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多