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中将方法添加到
当您键入以下Lua代码时:
1 | myUserdata:someMethod(arg1,arg2,arg3) |
假定
您的
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; } |
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() |