C++如何实现与Lua相互调用
今天小编给大家分享一下C++如何实现与Lua相互调用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。
概述
从本质上来看,其实说是不存在所谓的C++与lua的相互调用。lua是运行在C上的,简单来说lua的代码会被编译成字节码在被C语言的语法运行。在C++调用lua时,其实是解释运行lua文件编译出来的字节码。lua调用C++其实还是解释运行lua文件编译出来的字节码的语义是调用lua栈上的C++函数。
示例
来看下面这段代码:
C++
#include "Inc/lua.h"#include "Inc/lauxlib.h"#include "Inc/lualib.h"#include "Inc/lobject.h"}using std::cout;using std::endl;int CAdd(lua_State* L){int a = lua_tonumber(L, 2);int b = lua_tonumber(L, 1);;int sum = a + b;lua_pushnumber(L, sum);return 1;}int main(){lua_State* L = luaL_newstate();luaL_openlibs(L);lua_register(L, "CAdd", CAdd);int stat = luaL_loadfile(L, "Test.lua") | lua_pcall(L, 0, 0, 0);if (stat){cout << "error" << endl;}else{cout << "succ" << endl;}lua_close(L);return 0;}
lua
local x = CAdd(1, 2)print("x = " .. tostring(x))
运行结果:
考虑上述C++代码luaL_loadfile去加载并调用lua,lua又调用了C++注册到lua虚拟机里的CAdd函数并正确打印了返回值,结果如图所示。到底发生了什么?
C++调用lua
C++调用lua时,是对lua代码进行编译生成字节码,在运行时对字节码使用C的语法解释运行。
对luaL_loadfile调试,跟到f_parser:
static void f_parser (lua_State *L, void *ud) { LClosure *cl; struct SParser *p = cast(struct SParser *, ud); int c = zgetc(p->z); if (c == LUA_SIGNATURE[0]) { checkmode(L, p->mode, "binary"); cl = luaU_undump(L, p->z, p->name); } else { checkmode(L, p->mode, "text"); cl = luaY_parser(L, p->z, &p->buff, &p->dyd, p->name, c); } lua_assert(cl->nupvalues == cl->p->sizeupvalues); luaF_initupvals(L, cl);}
简单来说,parser根据输入进行词法,语法分析进行编码生成闭包,然后推入栈中等待调用。来看几个用到的数据结构。
LClosure
typedef struct LClosure { ClosureHeader; struct Proto *p; UpVal *upvals[1]; //被捕获的外局部变量} LClosure;
这是lua的闭包,此外还有CClosure是c的闭包,下面lua调用C++会提到,它们被Closure联合体包裹。
Proto
typedef struct Proto { CommonHeader; lu_byte numparams; lu_byte is_vararg; lu_byte maxstacksize; int sizeupvalues; int sizek; int sizecode; int sizelineinfo; int sizep; int sizelocvars; int linedefined; int lastlinedefined; TValue *k; Instruction *code; //codes struct Proto **p; int *lineinfo; LocVar *locvars; Upvaldesc *upvalues; struct LClosure *cache; TString *source; GCObject *gclist;} Proto;
Instruction *code;注意这个变量,这个变量就是指向我们编译后生成字节码数组的指针。
FuncState
typedef struct FuncState { Proto *f; struct FuncState *prev; struct LexState *ls; struct BlockCnt *bl; int pc; int lasttarget; int jpc; int nk; int np; int firstlocal; short nlocvars; lu_byte nactvar; lu_byte nups; lu_byte freereg; } FuncState;
FuncState互相是嵌套的,外部FuncState保存了内部的部分信息,最外部的FuncState的f成员保存了编译的所有字节码,并传递给闭包LClosure。
编译lua流程
以加载lua脚本为例。
f_parser调用luaY_parser分析,并初始化Upvalues(外局部变量)。
luaY_parser 使用LexState包裹FuncState调用luaX_next进行进一步分析,其结果保存到Proto结构的code数组中,传递给LClosure并推入栈中。
luaX_next循环分析,依据词法,语法规则调用luaK_code生成字节码。
部分代码:
static void statement (LexState *ls) { int line = ls->linenumber; enterlevel(ls); switch (ls->t.token) { case ';': { luaX_next(ls); break; } case TK_IF: { ifstat(ls, line); break; } //..................... }}
运行
编译代码后,便可对闭包进行解析运行了。调试代码上述 lua_pcall(L, 0, 0, 0) 代码,跟到luaD_call:
void luaD_call (lua_State *L, StkId func, int nResults) { if (++L->nCcalls >= LUAI_MAXCCALLS) stackerror(L); if (!luaD_precall(L, func, nResults)) luaV_execute(L); L->nCcalls--;}}
首先调用luaD_precall进行预备工作,lua_state扩展base_ci(CallInfo类型)数组创建一个新元素保存括虚拟机的指令指针(lua_state->savedpc)在内的调用堆栈的状态以便调用结束后恢复调用堆栈,并把指令指针指向该闭包的指令数组(Closure->p->codes)。
然后调用luaV_execute循环取出指令运行。
luaV_execute解释执行部分代码:
void luaV_execute (lua_State *L) { CallInfo *ci = L->ci; LClosure *cl; TValue *k; StkId base; ci->callstatus |= CIST_FRESH; newframe: lua_assert(ci == L->ci); cl = clLvalue(ci->func); k = cl->p->k; base = ci->u.l.base; for (;;) { Instruction i; StkId ra; vmfetch(); vmdispatch (GET_OPCODE(i)) { vmcase(OP_MOVE) { setobjs2s(L, ra, RB(i)); vmbreak; } //............................ }}
CallInfo
函数执行时,lua_state通过CallInfo 数据结构了解函数的状态信息,并通过CallInfo组base_ci的上下生长来维护调用堆栈。
typedef struct CallInfo { StkId func; StkIdtop; struct CallInfo *previous, *next; union { struct { StkId base; const Instruction *savedpc; } l; struct { lua_KFunction k; ptrdiff_t old_errfunc; lua_KContext ctx; } c; } u; ptrdiff_t extra; short nresults; unsigned short callstatus;} CallInfo;
lua调用C++
lua调用C++,是上述C++调用lua时即c的语法解释运行lua代码生成的字节码的一种情况,即取出lua状态机全局表中的CClosure中的函数指针运行。
来看下向lua状态机注册C++函数lua_register
#define lua_pushcfunction(L,f)lua_pushcclosure(L, (f), 0)#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n)))LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) { lua_lock(L); if (n == 0) { setfvalue(s2v(L->top), fn); api_incr_top(L); } else { CClosure *cl; api_checknelems(L, n); api_check(L, n <= MAXUPVAL, "upvalue index too large"); cl = luaF_newCclosure(L, n); cl->f = fn; L->top -= n; while (n--) { setobj2n(L, &cl->upvalue[n], s2v(L->top + n)); } setclCvalue(L, s2v(L->top), cl); api_incr_top(L); luaC_checkGC(L); } lua_unlock(L);}
可以看到这里最终创建了一个CCloseure,包裹住lua_CFunction类型的函数指针并推入栈顶和放入全局表中。
typedef int (*lua_CFunction) (lua_State *L);typedef struct CClosure { ClosureHeader; lua_CFunction f; TValue upvalue[1]; } CClosure;
可以看到CClosure包含了一个lua_CFunction类型的函数指针和upvalue的链表
解释运行调用语义
循环解释字节码语义的关于调用的部分
void luaV_execute (lua_State *L, CallInfo *ci) {//...vmcase(OP_CALL) { int b = GETARG_B(i); int nresults = GETARG_C(i) - 1; if (b != 0) L->top = ra + b; ProtectNT(luaD_call(L, ra, nresults)); vmbreak; }//...}
可以看到调用语义的解释调用了luaD_call
void luaD_call (lua_State *L, StkId func, int nresults) { lua_CFunction f; retry: switch (ttypetag(s2v(func))) { case LUA_VCCL: f = clCvalue(s2v(func))->f; goto Cfunc; case LUA_VLCF: f = fvalue(s2v(func)); Cfunc: { int n; CallInfo *ci = next_ci(L); checkstackp(L, LUA_MINSTACK, func); ci->nresults = nresults; ci->callstatus = CIST_C; ci->top = L->top + LUA_MINSTACK; ci->func = func; L->ci = ci; lua_assert(ci->top <= L->stack_last); if (L->hookmask & LUA_MASKCALL) { int narg = cast_int(L->top - func) - 1; luaD_hook(L, LUA_HOOKCALL, -1, 1, narg); } lua_unlock(L); n = (*f)(L); lua_lock(L); api_checknelems(L, n); luaD_poscall(L, ci, n); break; }//...
可以看到这里取到了上述Closure中的函数指针并进行调用。
以上就是“C++如何实现与Lua相互调用”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注编程网行业资讯频道。
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341