对于一个在C++创建的类对象,lua中如何调用这个对象的C++接口?进一步,如果我们想在lua中实现对这个C++类的接口扩展,该如何实现?
二、lua对于类似于C++中meta类型的支持
在lua中,为了模拟对于C++中面向对象中一个类接口的支持,提供一个专门的"NameSpace:function"类型的语法结构。
1、如何声明
从解析的地方看,这里是自动在函数的参数列表的最开始位置添加了一个self变量。 lua-5.3.4\src\lparser.c static void body (LexState *ls, expdesc *e, int ismethod, int line) { /* body -> '(' parlist ')' block END */ FuncState new_fs; BlockCnt bl; new_fs.f = addprototype(ls); new_fs.f->linedefined = line; open_func(ls, &new_fs, &bl); checknext(ls, '('); if (ismethod) { new_localvarliteral(ls, "self"); /* create 'self' parameter */ adjustlocalvars(ls, 1); } …… } 需要注意的是,这个地方是将":"作为函数名的一部分处理的。从这个解析可以看到,函数名的命名规则决定了它是否是一个“method”,但同时这个变量名也会被拆分,同样作为一个"fieldsel"语法,这意味着和table的field一样,这个地方会在table中添加一个名字为":"后面字符串的field,并且它指向的是这个函数。 static int funcname (LexState *ls, expdesc *v) { /* funcname -> NAME {fieldsel} [':' NAME] */ int ismethod = 0; singlevar(ls, v); while (ls->t.token == '.') fieldsel(ls, v); if (ls->t.token == ':') { ismethod = 1; fieldsel(ls, v); } return ismethod; }
2、如何调用
这里可以看到,在suffixedexp函数中传入的变量v表示的是之前解析出来的数值(例如some:little这种语法中,这个v表示的就是some这个表达式) static void suffixedexp (LexState *ls, expdesc *v) { /* suffixedexp -> primaryexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs } */ FuncState *fs = ls->fs; int line = ls->linenumber; primaryexp(ls, v); for (;;) { …… case ':': { /* ':' NAME funcargs */ expdesc key; luaX_next(ls); checkname(ls, &key); luaK_self(fs, v, &key); funcargs(ls, v, line); break; …… } 在这个地方生成了一个OP_SELF指令,并且预分配了两个寄存器,分别用来存储self和查询获得的function地址。 lua-5.3.4\src\lcode.c /* ** Emit SELF instruction (convert expression 'e' into 'e:key(e,'). */ void luaK_self (FuncState *fs, expdesc *e, expdesc *key) { int ereg; luaK_exp2anyreg(fs, e); ereg = e->u.info; /* register where 'e' was placed */ freeexp(fs, e); e->u.info = fs->freereg; /* base register for op_self */ e->k = VNONRELOC; /* self expression has a fixed register */ luaK_reserveregs(fs, 2); /* function and 'self' produced by op_self */ luaK_codeABC(fs, OP_SELF, e->u.info, ereg, luaK_exp2RK(fs, key)); freeexp(fs, key); }
3、如何执行
这里看到一个特殊的处理:除了将self(也就是前面suffixedexp压入的v值)通过setobjs2s(L, ra + 1, rb);原封不动的压入堆栈之外,还进行了一个额外的查找动作,就是从v中查找函数的内容,这个地方是一个“普通”的table中查找指定成员的操作,所以这个查找也可以通过它的__index成员完成。 void luaV_execute (lua_State *L) { …… vmcase(OP_SELF) { const TValue *aux; StkId rb = RB(i); TValue *rc = RKC(i); TString *key = tsvalue(rc); /* key must be a string */ setobjs2s(L, ra + 1, rb); if (luaV_fastget(L, rb, key, aux, luaH_getstr)) { setobj2s(L, ra, aux); } else Protect(luaV_finishget(L, rb, rc, ra, aux)); vmbreak; } …… }
4、当直接使用类名调用函数时
这种情况下,self指向的就是metatable本身,大致略等于C++中调用静态成员函数(self指向metatable本身) tsecer@harry: cat lua.static.call.method.lua meta = { x = 1111, }
function meta:func1() print("xxxx") print(self) for k,v in pairs(self) do print(k, v) end end
meta:func1() tsecer@harry: /home/tsecer/study/lua-5.3.4/src/lua lua.static.call.method.lua xxxx table: 0x64b4b0 x 1111 func1 function: 0x64b0e0 tsecer@harry:
5、总结
总起来说,定义的时候是metatable:funcname,调用的时候是obj:funcname。这一点的确是和C++的语法是神似的。
三、如何使用
0、限制
这里的限制依然在于lua中只有table和userdata支持(lightuserdata也不支持)metatable,所以想使用lua的metatable方法一定需要使用userdata结构。
1、在lua中调用C++中对象方法
tsecer@harry: cat lua.oo.cpp extern "C" { #include <lua.h> #include <lualib.h> #include <lauxlib.h> }
struct AA { AA(int x): m_x(x) {}
int func1(int x) { printf("in %s x %d m_x %d\n", __func__, x, m_x); return 0; } int func2(int x, int y) { printf("in %s x %d y %d m_x %d\n", __func__, x, m_x); return 0; } int m_x; };
int CallAAFunc1(lua_State *L) { //第一个参数保存self指针 AA *pobj = *(AA**)lua_touserdata(L, 1); //接下来保存参数列表 int parm1 = lua_tonumber(L, 2); //调用函数 pobj->func1(parm1); return 0; }
int CallAAFunc2(lua_State *L) { //由于是通过 AAObj::Func2调用,所以状态机中会有三个参数 //从1到3一次为:self也就是AAObj对象,调用时的两个参数 AA *pobj = *(AA**)lua_touserdata(L, 1); int parm1 = lua_tonumber(L, 2), parm2 = lua_tonumber(L, 3); pobj->func2(parm1, parm2); return 0; }
//注册个lua的__index方法,当lua需要查找一个结构当前不存在的filed时调用 int Index(lua_State *L) { //第一个参数为变量本身,它的metatable中存储有函数到Cfunction的映射 int imetatable = lua_getmetatable(L, 1); if (imetatable != 1) { printf("get metatable failed \n"); return -1; }
//第二个参数为函数名 const char *funcname = lua_tostring(L, 2); if (funcname == nullptr) { printf("get funcname failed \n"); return -1; }
//从metatable中以函数名为键值查找cfunction lua_getfield(L, -1, funcname);
return 1; }
int CreateMetaTable(lua_State *L) { //由于lua中的metatable也是一个简单的table,所以这个地方首先直接创建一个普通的table lua_createtable(L, 0, 0); //创建metatable中的__index方法 lua_pushstring(L, "__index"); //创建该方法对应的C函数 lua_pushcfunction(L, Index); //将新生成的metatable的__index设置为新创建的cfunction lua_rawset(L, -3);
//在metatable中添加转发函数 //函数名 lua_pushstring(L, "Func1"); //C函数地址 lua_pushcfunction(L, CallAAFunc1); //设置hashtable lua_rawset(L, -3); //在metatable中设置Func2为自定义的转发函数 lua_pushstring(L, "Func2"); lua_pushcfunction(L, CallAAFunc2); lua_rawset(L, -3);
//将AA作为全局变量添加到lua虚拟机中 lua_setglobal(L, "AA"); return 1; }
int BindObj(lua_State *L, AA *Obj) { //创建一个只包含一个指针类型的userdata,其中保存C++中创建对象的地址 *(AA**)lua_newuserdata(L, sizeof(Obj)) = Obj; //将新创建变量赋值给lua中全局变量AAObj lua_setglobal(L, "AAObj"); //从lua中查找到AAObj变量(结果在栈顶) lua_getglobal(L, "AAObj"); //查找到之前创建的metatable:AA变量 lua_getglobal(L, "AA"); //将堆栈中-2位置变量(AAObj)的metatable设置为栈顶变量(AA变量) lua_setmetatable(L, -2); //在lua虚拟机中执行AAObj(userdata存储了对象指针的lua变量) //由于AAObj是一个userdata,所以会调用它metatable中的__index方法,也就是前面设置的Index函数 //传入的参数是AAObj和需要查找的成员Func1/Func2 const char *luascript = "AAObj:Func1(1)" "AAObj:Func2(2, 3)"; if (luaL_loadstring(L, luascript) == LUA_OK) { if (lua_pcall(L, 0, 0, 0) == LUA_OK) { lua_pop(L, lua_gettop(L)); } } else { printf("load failed\n"); return -1; }
}
int main(int argc, char ** argv) {
lua_State *L = luaL_newstate(); luaL_openlibs(L); //lua_register(L, "CreateObject", CreateObject); CreateMetaTable(L); AA a1(1111), a2(2222);
BindObj(L, &a1); BindObj(L, &a2);
lua_close(L); return 0; }
tsecer@harry: ./a.out in func1 x 1 m_x 1111 in func2 x 2 y 1111 m_x 1 in func1 x 1 m_x 2222
2、在C++中调用lua method
下面的例子比较简单,但是里面展示通过userdata的自定义结构配合metatable完成对C++不同类型对象的访问 tsecer@harry: cat lua.oo.cpp extern "C" { #include <lua.h> #include <lualib.h> #include <lauxlib.h> }
#include "string.h" //strcmp
struct AA { AA(int x): m_x(x) {}
int func1(int x) { printf("in %s x %d m_x %d\n", __func__, x, m_x); return 0; } int func2(int x, int y) { printf("in %s x %d y %d m_x %d\n", __func__, x, m_x); return 0; } int m_x; };
int CallAAFunc1(lua_State *L) { //第一个参数保存self指针 AA *pobj = *(AA**)lua_touserdata(L, 1); //接下来保存参数列表 int parm1 = lua_tonumber(L, 2); //调用函数 pobj->func1(parm1); return 0; }
int CallAAFunc2(lua_State *L) { //由于是通过 AAObj::Func2调用,所以状态机中会有三个参数 //从1到3一次为:self也就是AAObj对象,调用时的两个参数 AA *pobj = *(AA**)lua_touserdata(L, 1); int parm1 = lua_tonumber(L, 2), parm2 = lua_tonumber(L, 3); pobj->func2(parm1, parm2); return 0; }
//注册个lua的__index方法,当lua需要查找一个结构当前不存在的filed时调用 int Index(lua_State *L) { //第一个参数为变量本身,它的metatable中存储有函数到Cfunction的映射 int imetatable = lua_getmetatable(L, 1); if (imetatable != 1) { printf("get metatable failed \n"); return -1; }
//第二个参数为函数名 const char *funcname = lua_tostring(L, 2); if (funcname == nullptr) { printf("get funcname failed \n"); return -1; }
AA *pobj = *(AA**)lua_touserdata(L, 1); if (strcmp(funcname, "m_x") == 0) {//对于成员做一个特殊处理 lua_pushinteger(L, pobj->m_x); } else { //从metatable中以函数名为键值查找cfunction lua_getfield(L, -1, funcname); } return 1; }
int CreateMetaTable(lua_State *L) { //由于lua中的metatable也是一个简单的table,所以这个地方首先直接创建一个普通的table lua_createtable(L, 0, 0); //创建metatable中的__index方法 lua_pushstring(L, "__index"); //创建该方法对应的C函数 lua_pushcfunction(L, Index); //将新生成的metatable的__index设置为新创建的cfunction lua_rawset(L, -3);
//在metatable中添加转发函数 //函数名 lua_pushstring(L, "Func1"); //C函数地址 lua_pushcfunction(L, CallAAFunc1); //设置hashtable lua_rawset(L, -3); //在metatable中设置Func2为自定义的转发函数 lua_pushstring(L, "Func2"); lua_pushcfunction(L, CallAAFunc2); lua_rawset(L, -3);
//将AA作为全局变量添加到lua虚拟机中 lua_setglobal(L, "AA"); return 1; }
int BindObj(lua_State *L, AA *Obj) { //现在lua中定义一个方法(method),这样在后面才能通过函数名(Func3)调用 const char *luascript = "function AA:Func3(x) print(self.m_x * 100000 + x); end"; if (luaL_loadstring(L, luascript) == LUA_OK) { if (lua_pcall(L, 0, 0, 0) == LUA_OK) { lua_pop(L, lua_gettop(L)); } } else { printf("load failed\n"); return -1; }
//从AA表中找到Func3地址 lua_getglobal(L, "AA"); //lua_pushvalue(L, -1); //查找其中的Func3方法 lua_getfield(L, -1, "Func3"); //创建一个userdata并把它保留在栈中,作为method调用的self参数 //创建一个只包含一个指针类型的userdata,其中保存C++中创建对象的地址 *(AA**)lua_newuserdata(L, sizeof(Obj)) = Obj; //从lua中读取AA table,该值作为新创建对象的metatable lua_getglobal(L, "AA"); //将新创建对象的metatable(-2中保存的下标)设置为AA(table,栈中下标为-1) //该操作执行之后堆栈中的AA table会被自动从堆栈中弹出 lua_setmetatable(L, -2); //函数调用参数入栈 lua_pushinteger(L, 1234);
//执行函数调用 lua_call(L, 2, 1);
return 0; }
int main(int argc, char ** argv) {
lua_State *L = luaL_newstate(); luaL_openlibs(L); //lua_register(L, "CreateObject", CreateObject); CreateMetaTable(L); AA a1(1111);
BindObj(L, &a1);
lua_close(L); return 0; }
tsecer@harry:
四、unlua如何实现
可以看到,UnLua也是使用userdata结构,并在其中存储C++对象地址,并为对象设置专有的metatable实现。 UnLua-master\Plugins\UnLua\Source\UnLua\Private\LuaCore.cpp /** * Push a UObject to Lua stack */ void PushObjectCore(lua_State *L, UObjectBaseUtility *Object) { if (!Object) { lua_pushnil(L); return; }
void **Dest = (void**)lua_newuserdata(L, sizeof(void*)); // create a userdata *Dest = Object; // store the UObject address …… // the UObject is object instance TStringConversion<TStringConvert<TCHAR, ANSICHAR>> ClassName(*FString::Printf(TEXT("%s%s"), Class->GetPrefixCPP(), *Class->GetName())); bSuccess = TryToSetMetatable(L, ClassName.Get()); …… }
|
请发表评论