從本質上來看,其實說是不存在所謂的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指令碼為例。
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中的函數指標並進行呼叫。