C++實現(xiàn)與Lua相互調(diào)用的示例詳解
概述
從本質(zhì)上來看,其實說是不存在所謂的C++與lua的相互調(diào)用。lua是運行在C上的,簡單來說lua的代碼會被編譯成字節(jié)碼在被C語言的語法運行。在C++調(diào)用lua時,其實是解釋運行l(wèi)ua文件編譯出來的字節(jié)碼。lua調(diào)用C++其實還是解釋運行l(wèi)ua文件編譯出來的字節(jié)碼的語義是調(diào)用lua棧上的C++函數(shù)。
示例
來看下面這段代碼:
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))
運行結(jié)果:

考慮上述C++代碼luaL_loadfile去加載并調(diào)用lua,lua又調(diào)用了C++注冊到lua虛擬機(jī)里的CAdd函數(shù)并正確打印了返回值,結(jié)果如圖所示。到底發(fā)生了什么?
C++調(diào)用lua
C++調(diào)用lua時,是對lua代碼進(jìn)行編譯生成字節(jié)碼,在運行時對字節(jié)碼使用C的語法解釋運行。
對luaL_loadfile調(diào)試,跟到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根據(jù)輸入進(jìn)行詞法,語法分析進(jìn)行編碼生成閉包,然后推入棧中等待調(diào)用。來看幾個用到的數(shù)據(jù)結(jié)構(gòu)。
LClosure
typedef struct LClosure {
ClosureHeader;
struct Proto *p;
UpVal *upvals[1]; //被捕獲的外局部變量
} LClosure;
這是lua的閉包,此外還有CClosure是c的閉包,下面lua調(diào)用C++會提到,它們被Closure聯(lián)合體包裹。
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;注意這個變量,這個變量就是指向我們編譯后生成字節(jié)碼數(shù)組的指針。
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保存了內(nèi)部的部分信息,最外部的FuncState的f成員保存了編譯的所有字節(jié)碼,并傳遞給閉包LClosure。
編譯lua流程
以加載lua腳本為例。
- f_parser調(diào)用luaY_parser分析,并初始化Upvalues(外局部變量)。
- luaY_parser 使用LexState包裹FuncState調(diào)用luaX_next進(jìn)行進(jìn)一步分析,其結(jié)果保存到Proto結(jié)構(gòu)的code數(shù)組中,傳遞給LClosure并推入棧中。
- luaX_next循環(huán)分析,依據(jù)詞法,語法規(guī)則調(diào)用luaK_code生成字節(jié)碼。
部分代碼:
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;
}
//.....................
}
}
運行
編譯代碼后,便可對閉包進(jìn)行解析運行了。調(diào)試代碼上述 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--;
}
}
首先調(diào)用luaD_precall進(jìn)行預(yù)備工作,lua_state擴(kuò)展base_ci(CallInfo類型)數(shù)組創(chuàng)建一個新元素保存括虛擬機(jī)的指令指針(lua_state->savedpc)在內(nèi)的調(diào)用堆棧的狀態(tài)以便調(diào)用結(jié)束后恢復(fù)調(diào)用堆棧,并把指令指針指向該閉包的指令數(shù)組(Closure->p->codes)。
然后調(diào)用luaV_execute循環(huán)取出指令運行。
luaV_execute解釋執(zhí)行部分代碼:
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
函數(shù)執(zhí)行時,lua_state通過CallInfo 數(shù)據(jù)結(jié)構(gòu)了解函數(shù)的狀態(tài)信息,并通過CallInfo組base_ci的上下生長來維護(hù)調(diào)用堆棧。
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調(diào)用C++
lua調(diào)用C++,是上述C++調(diào)用lua時即c的語法解釋運行l(wèi)ua代碼生成的字節(jié)碼的一種情況,即取出lua狀態(tài)機(jī)全局表中的CClosure中的函數(shù)指針運行。
來看下向lua狀態(tài)機(jī)注冊C++函數(shù)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);
}
可以看到這里最終創(chuàng)建了一個CCloseure,包裹住lua_CFunction類型的函數(shù)指針并推入棧頂和放入全局表中。
typedef int (*lua_CFunction) (lua_State *L);
typedef struct CClosure {
ClosureHeader;
lua_CFunction f;
TValue upvalue[1]; /* list of upvalues */
} CClosure;
可以看到CClosure包含了一個lua_CFunction類型的函數(shù)指針和upvalue的鏈表
解釋運行調(diào)用語義
循環(huán)解釋字節(jié)碼語義的關(guān)于調(diào)用的部分
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;
}
//...
}
可以看到調(diào)用語義的解釋調(diào)用了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中的函數(shù)指針并進(jìn)行調(diào)用。
到此這篇關(guān)于C++實現(xiàn)與Lua相互調(diào)用的示例詳解的文章就介紹到這了,更多相關(guān)C++ Lua相互調(diào)用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++構(gòu)造析構(gòu)賦值運算函數(shù)應(yīng)用詳解
構(gòu)造函數(shù)主要作用在于創(chuàng)建對象時為對象的成員屬性賦值,構(gòu)造函數(shù)由編譯器自動調(diào)用,無須手動調(diào)用;析構(gòu)函數(shù)主要作用在于對象銷毀前系統(tǒng)自動調(diào)用,執(zhí)行一 些清理工作2022-09-09
C++?LeetCode1827題解最少操作使數(shù)組遞增
這篇文章主要為大家介紹了C++?LeetCode1827題解最少操作使數(shù)組遞增示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
關(guān)于C++出現(xiàn)Bus error問題的排查與解決
項目代碼中經(jīng)常出現(xiàn)莫名其妙的Bus error問題,并且代碼中增加很多try catch 后依然不能將錯誤捕獲,一旦Bus erro出現(xiàn),進(jìn)程直接崩潰掉,所以本文給大家介紹了關(guān)于C++出現(xiàn)Bus error問題的排查與解決,需要的朋友可以參考下2024-01-01
關(guān)于C++中push_back()函數(shù)的用法及代碼實例
push_back是vector的一個方法,表示將一個元素存儲到容器的末尾,下面這篇文章主要給大家介紹了關(guān)于C++中push_back()函數(shù)用法的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11

