Lua代碼運行過程中,可能會出現異常狀態,比如非法地址訪問、遇到未定義符號、或者斷言失敗等,由於異常出現的地方不定,所以我們需要用一些方法來獲取異常信息,找到出現異常的原因。
在C語言代碼中處理Lua腳本運行產生的異常:
要能在發生異常后可以在C語言代碼中獲取到異常信息,就必須通過lua_pcall,或者lua_pcallk函數來運行Lua代碼,如果直接使用lua_call函數來運行Lua代碼,當發生異常后,Lua解析器不會保留異常信息,而是會調用系統函數abort導致整個程序運行中止。
對於lua_pcall、lua_pcallk這兩個函數,前者實際上是一個宏,是對后者做了一下簡單封裝后的結果,后者用得不多,這里不再做介紹。故名思意,lua_pcall函數是以保護模式運行Lua代碼,當Lua代碼中遇到異常時,lua_pcall函數不會直接調用abort函數導致程序卡死,而是會進行異常處理,然后恢復運行Lua代碼之前的狀態,然后返回。
Main.c: |
int main(void) { lua_State* L = NULL; int status; L = luaL_newstate(); luaL_openlibs(L); luaL_loadfile(L, "test_01.lua"); status=lua_pcall(L, 0, LUA_MULTRET,0); lua_remove(L, 1);/*將ExceptionHandle從棧中刪掉,注意這里不用lua_pop,而是lua_remove是因為此時ExceptionHandle不一定在棧頂*/ if (status != LUA_OK) { /*將錯誤代碼和棧頂的字符串打印出來*/ printf("error code:%d,msg:%s\r\n",status,lua_tostring(L,-1)); } lua_close(L); return 0; } |
Test_01.lua |
Hello() |
運行結果: |
再試一下使用自定義異常處理函數的情況,這里Main.c修改如下,Lua代碼不修改。
Main.c: |
int ExceptionHandle(lua_State* L) { const char* msg = lua_tostring(L, -1); luaL_traceback(L, L, msg, 1); printf("%s\r\n", lua_tostring(L, -1)); return 0; } int main(void) { lua_State* L = NULL; int status; L = luaL_newstate(); luaL_openlibs(L); lua_pushcfunction(L, ExceptionHandle); luaL_loadfile(L, "test_01.lua"); status=lua_pcall(L, 0, LUA_MULTRET,1); lua_remove(L, 1);/*將ExceptionHandle從棧中刪掉,注意這里不用lua_pop,而是lua_remove是因為此時ExceptionHandle不一定在棧頂*/ if (status != LUA_OK) { /*將錯誤代碼和棧頂的字符串打印出來*/ printf("error code:%d,msg:%s\r\n",status,lua_tostring(L,-1)); } lua_close(L); return 0; } |
運行結果: |
int luaD_precall (lua_State *L, StkId func, int nresults) { /* do something */ switch (ttype(func)) {/* 判斷函數類型,是C函數還是Lua函數 */ case LUA_TCCL: /* C closure */ case LUA_TLCF: /* light C function */ tryfuncTM(L, func); /* try to get '__call' metamethod */ return luaD_precall(L, func, nresults); /* 如果發生異常,則不會執行到這里 */ |
static void tryfuncTM (lua_State *L, StkId func) { const TValue *tm = luaT_gettmbyobj(L, func, TM_CALL); /*do something*/ if (!ttisfunction(tm)) luaG_typeerror(L, func, "call");/*拋出異常*/ /*do something*/ } |
接下來看luaG_errormsg函數,這個函數解釋了為啥簡單錯誤信息會被自定義異常處理函數覆蓋。
l_noret luaG_errormsg (lua_State *L) { if (L->errfunc != 0) { /* is there an error handling function? */ StkId errfunc = restorestack(L, L->errfunc); setobjs2s(L, L->top, L->top - 1); /* move argument */ setobjs2s(L, L->top - 1, errfunc); /* push function */ L->top++; /* assume EXTRA_STACK */ |
int luaD_pcall (lua_State *L, Pfunc func, void *u,ptrdiff_t old_top, ptrdiff_t ef) { int status; /* do something:保存調用前的狀態 */ L->errfunc = ef; status = luaD_rawrunprotected(L, func, u);/*執行Lua腳本 */ if (status != LUA_OK) { /* 如果執行得有問題 */ /* do something:獲取調用前的棧狀態 */ seterrorobj(L, status, oldtop); /* do something:還原調用前的狀態 */ } L->errfunc = old_errfunc; return status; } |
static void seterrorobj (lua_State *L, int errcode, StkId oldtop) { switch (errcode) { case LUA_ERRMEM: { /* do something */ break; } case LUA_ERRERR: { /* do something */ break; } default: { setobjs2s(L, oldtop, L->top - 1); /* 將錯誤信息壓倒舊棧指針指向的位置 */ break; } } L->top = oldtop + 1;/* 強制恢復棧頂指針,並將其加一 */ } |
可以看到,seterrorobj函數中有3個分支,分別是LUA_ERRMEM、LUA_ERRERR和default,這個分支代表Lua5.3參考手冊中的5中錯誤代碼:
這是在luaG_errormsg函數中對L->top強制加一的情況。