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强制加一的情况。