在 C/c++ 中实现 __index 元函数
implementing __index metafunction in C/c++
我有一个 C++ callback/functor 系统的脚本,它可以使用字符串 and/or 变体调用任何 "registered" 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 对象。但是,我无法在堆栈中找到 args。设置它的正确方法是什么?还是不可能?
好的,经过更多研究,我现在相信我不能使用 __index 元函数来调用带参数的 c 仿函数。它只将 table 名称和密钥传递给回调。
但是,对于任何感兴趣的人,它可以用于 table 类对象,但不能用于函数(因为参数不会被压入堆栈)。我会为我的 "property" 对象。它们没有参数,可以在 lua 中使用,如下所示...
local king = EmpireManager:king
king:name = "Arthur"
local name = king:name
这些正确link到并调用适当的C++ objects.functions
Actor::SetName(std::string name)
std::string Actor::GetName()
Lua 中的 userdata
类型绝对可以添加方法,如官方网站 Programming in Lua 指南中所述。
当您键入以下 Lua 代码时:
myUserdata:someMethod(arg1,arg2,arg3)
假设 myUserdata
是一个 "userdata" 对象,解释器将执行以下操作。
- 调用
getmetatable(myUserdata).__index(myUserdata,"someMethod")
获取someMethod
的值。
- 呼叫
someMethod(myUserdata,arg1,arg2,arg3)
。 someMethod
可以是任何可从 Lua 调用的东西。示例:Lua 或 C 函数,或具有 __call
元方法的 table/userdata。
您的 __index
元方法应该只是 return 一个实现该方法的函数(或另一个可从 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
元方法 return 编辑的函数的基本框架:
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.
}
我在从我的对象调用方法时遇到了同样的问题,并使用这个 post 来开发解决方案。
希望下面的例子对你有用。
#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
我有一个 C++ callback/functor 系统的脚本,它可以使用字符串 and/or 变体调用任何 "registered" 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 对象。但是,我无法在堆栈中找到 args。设置它的正确方法是什么?还是不可能?
好的,经过更多研究,我现在相信我不能使用 __index 元函数来调用带参数的 c 仿函数。它只将 table 名称和密钥传递给回调。
但是,对于任何感兴趣的人,它可以用于 table 类对象,但不能用于函数(因为参数不会被压入堆栈)。我会为我的 "property" 对象。它们没有参数,可以在 lua 中使用,如下所示...
local king = EmpireManager:king
king:name = "Arthur"
local name = king:name
这些正确link到并调用适当的C++ objects.functions
Actor::SetName(std::string name)
std::string Actor::GetName()
Lua 中的 userdata
类型绝对可以添加方法,如官方网站 Programming in Lua 指南中所述。
当您键入以下 Lua 代码时:
myUserdata:someMethod(arg1,arg2,arg3)
假设 myUserdata
是一个 "userdata" 对象,解释器将执行以下操作。
- 调用
getmetatable(myUserdata).__index(myUserdata,"someMethod")
获取someMethod
的值。 - 呼叫
someMethod(myUserdata,arg1,arg2,arg3)
。someMethod
可以是任何可从 Lua 调用的东西。示例:Lua 或 C 函数,或具有__call
元方法的 table/userdata。
您的 __index
元方法应该只是 return 一个实现该方法的函数(或另一个可从 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
元方法 return 编辑的函数的基本框架:
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.
}
我在从我的对象调用方法时遇到了同样的问题,并使用这个 post 来开发解决方案。
希望下面的例子对你有用。
#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