您好,登錄后才能下訂單哦!
今天小編給大家分享一下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時,是對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); /* read first character */ 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; /* number of fixed parameters */ lu_byte is_vararg; lu_byte maxstacksize; /* number of registers needed by this function */ int sizeupvalues; /* size of 'upvalues' */ int sizek; /* size of 'k' */ int sizecode; int sizelineinfo; int sizep; /* size of 'p' */ int sizelocvars; int linedefined; /* debug information */ int lastlinedefined; /* debug information */ TValue *k; /* constants used by the function */ Instruction *code; //codes struct Proto **p; /* functions defined inside the function */ int *lineinfo; /* map from opcodes to source lines (debug information) */ LocVar *locvars; /* information about local variables (debug information) */ Upvaldesc *upvalues; /* upvalue information */ struct LClosure *cache; /* last-created closure with this prototype */ TString *source; /* used for debug information */ GCObject *gclist; } Proto;
Instruction *code;注意這個變量,這個變量就是指向我們編譯后生成字節碼數組的指針。
FuncState
typedef struct FuncState { Proto *f; /* current function header */ struct FuncState *prev; /* enclosing function */ struct LexState *ls; /* lexical state */ struct BlockCnt *bl; /* chain of current blocks */ int pc; /* next position to code (equivalent to 'ncode') */ int lasttarget; /* 'label' of last 'jump label' */ int jpc; /* list of pending jumps to 'pc' */ int nk; /* number of elements in 'k' */ int np; /* number of elements in 'p' */ int firstlocal; /* index of first local var (in Dyndata array) */ short nlocvars; /* number of elements in 'f->locvars' */ lu_byte nactvar; /* number of active local variables */ lu_byte nups; /* number of upvalues */ lu_byte freereg; /* first free register */ } FuncState;
FuncState互相是嵌套的,外部FuncState保存了內部的部分信息,最外部的FuncState的f成員保存了編譯的所有字節碼,并傳遞給閉包LClosure。
以加載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; /* may be needed for error messages */ enterlevel(ls); switch (ls->t.token) { case ';': { /* stat -> ';' (empty statement) */ luaX_next(ls); /* skip ';' */ break; } case TK_IF: { /* stat -> ifstat */ 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)) /* is a Lua function? */ luaV_execute(L); /* call it */ 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; /* fresh invocation of 'luaV_execute" */ newframe: /* reentry point when frame changes (call/return) */ lua_assert(ci == L->ci); cl = clLvalue(ci->func); /* local reference to function's closure */ k = cl->p->k; /* local reference to function's constant table */ base = ci->u.l.base; /* local copy of function's base */ /* main loop of interpreter */ 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; /* function index in the stack */ StkId top; /* top for this function */ struct CallInfo *previous, *next; /* dynamic call link */ union { struct { /* only for Lua functions */ StkId base; /* base for this function */ const Instruction *savedpc; } l; struct { /* only for C functions */ lua_KFunction k; /* continuation in case of yields */ ptrdiff_t old_errfunc; lua_KContext ctx; /* context info. in case of yields */ } c; } u; ptrdiff_t extra; short nresults; /* expected number of results from this function */ unsigned short callstatus; } CallInfo;
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)); /* does not need barrier because closure is white */ } 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]; /* list of upvalues */ } 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) /* fixed number of arguments? */ L->top = ra + b; /* top signals number of arguments */ /* else previous instruction set top */ 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: /* C closure */ f = clCvalue(s2v(func))->f; goto Cfunc; case LUA_VLCF: /* light C function */ f = fvalue(s2v(func)); Cfunc: { int n; /* number of returns */ CallInfo *ci = next_ci(L); checkstackp(L, LUA_MINSTACK, func); /* ensure minimum stack size */ 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); /* do the actual call */ lua_lock(L); api_checknelems(L, n); luaD_poscall(L, ci, n); break; } //...
可以看到這里取到了上述Closure中的函數指針并進行調用。
以上就是“C++如何實現與Lua相互調用”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。