Lua接口学习

今天在程序里面使用lua_call,导致程序没有任何错误提示直接退出,找了好久原因,才知道lua_call不是一个受保护的方法,如果其中发生错误,会导致宿主程序直接退出

直到看到这篇文章才恍然大悟:最后查看官方文档,更换使用了lua_pcall。没有错误的问题真心可怕啊。。。

为何使用lua_atpanic
当调用无保护的lua_call后,如果调用栈发生错误(lua_error),那么默认行为是直接退出宿主程序。(可以参考这篇文章)
要避免这样的情况,一种方法是定义自己的panic函数,并作为参数调用lua_atpanic;此外为了避免退出宿主程序,自定义的panic函数应该永不返回(通常是做一个长跳转,令其跳转至lua_call调用点,不过这种做法几乎与lua_pcall无异)
lua_atpanic 1. lua_atpanic设置新panic函数并返回旧的panic函数。
2. 当在无保护环境下发生错误,lua调用当前的panic函数并呼叫exit(EXIT_FAILURE)退出宿主程序。
3. 我们可以提供自己的panic函数以避免退出宿主程序。(一种方法是做一个长跳转(long jump))
4. panic函数可以访问位于栈顶的错误讯息。
示例

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <stdio.h>
#include <setjmp.h> static jmp_buf jbuf; int
    panichandler(lua_State *L)    
    printf("%s\n", lua_tostring(L, 1));
    lua_pop(L, 1);
    longjmp(&jbuf, 1);  
}
int main(int argc, char *argv[])
lua_State *L;
int ret = 0;
if ((L = lua_open()) == NULL) {
    printf("lua_open() failed!\n");
    return 1;
} luaL_openlibs(L);
lua_atpanic(L, panichandler);
if (luaL_loadfile(L, "main.lua") == 0) {  
      if (setjmp(&jbuf) == 0)   /* first jmp */
          lua_call(L, 0, 0);    
      else                      /* second jmp, error */
          ret = 1;      else
      ret = 1; lua_close(L);
    return ret;
}



luaL_checkxxx和luaL_argcheck内部调用luaL_error,luaL_error内部调用lua_error,在调用lua_error前会格式化错误信息压入栈顶,最后调用lua_error,lua_error不会返回,而是做一个长跳转。所以在C函数中使用return luaL_error也不会返回,而是跳转到调用lua_pcall点 即lua_pcall返回。

C库函数的例子
int dir(lua_State *L)
{
    HANDLE              h;
    WIN32_FIND_DATAA    fd;
    const char         *dirpath;
    dirpath = luaL_checkstring(L, 1); /* 一旦出错立刻压入错误,并从lua_pcall返回,luaL_error也一样 */
    if ((h = FindFirstFileA(dirpath, &fd)) == INVALID_HANDLE_VALUE)
        return luaL_error(L, "failed to find '%s', win32 error code %d", dirpath, GetLastError());
    return 0;
}

lua_error在内部调用luaD_throw函数:
void luaD_throw (lua_State *L, int errcode) {
if (L->errorJmp) {
    L->errorJmp->status = errcode;
    LUAI_THROW(L, L->errorJmp);
}
else {
    L->status = cast_byte(errcode);
    if (G(L)->panic) {
      resetstack(L, errcode);
      lua_unlock(L);
      G(L)->panic(L);
    }
    exit(EXIT_FAILURE);
}
}
1. 当一个跳转点存在时,lua_error将做一个长跳转
2. 否则调用一个panic函数(如果存在),并退出宿主程序。

现在的问题是跳转点何时会存在,位于何处?
答案:LUA库函数中运行于保护模式的函数会设置跳转点,这包括lua_pcall, lua_cpcall, 以及由此函数衍生的一系列宏(如lua_dofile, luaL_dostring等)
观察lua_pcall的源码,该函数调用luaD_rawrunprotected
int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) {
struct lua_longjmp lj;
lj.status = 0;
lj.previous = L->errorJmp; /* chain new error handler */
L->errorJmp = &lj;
LUAI_TRY(L, &lj,
    (*f)(L, ud);
);
L->errorJmp = lj.previous; /* restore old error handler */
return lj.status;
}
这段代码显示了设置跳转点的过程。相比之下无保护的lua_call就没有类似的代码。
1.调用lua_pcall后,如果调用栈上发生任何错误(即lua_error被调用),lua_error将直接跳转至lua_pcall的调用点,并从lua_pcall返回。
2.调用lua_call后发生错误,lua_error将直接调用panic函数并退出宿主程序;除非通过设置自定义panic函数永不返回(通常是做长跳转以避免退出)。

当使用lua_call时,用lua_atpanic为其设置panic函数

为何使用lua_atpanic
当调用无保护的lua_call后,如果调用栈发生错误(lua_error),那么默认行为是直接退出宿主程序。要避免这样的情况,一种方法是定义自己的panic函数,并作为参数调用lua_atpanic;此外为了避免退出宿主程序,自定义的panic函数应该永不返回(通常是做一个长跳转,令其跳转至lua_call调用点,不过这种做法几乎与lua_pcall无异)
1. lua_atpanic设置新panic函数并返回旧的panic函数。
2. 当在无保护环境下发生错误,lua调用当前的panic函数并呼叫exit(EXIT_FAILURE)退出宿主程序。
3. 我们可以提供自己的panic函数以避免退出宿主程序。(一种方法是做一个长跳转(long jump))
4. panic函数可以访问位于栈顶的错误讯息。


示例

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <stdio.h>
#include <setjmp.h>

static jmp_buf jbuf;

int
panichandler(lua_State *L)
{  
    printf("%s\n", lua_tostring(L, 1));
    lua_pop(L, 1);
    longjmp(&jbuf, 1);  
}

int main(int argc, char *argv[])
{
  lua_State *L;
  int ret = 0;

  if ((L = lua_open()) == NULL)
  {
     printf("lua_open() failed!\n");
     return 1;
   }

   luaL_openlibs(L);
   lua_atpanic(L, panichandler);

  if (luaL_loadfile(L, "main.lua") == 0)
   {  
      if (setjmp(&jbuf) == 0)   /* first jmp */
          lua_call(L, 0, 0);    
      else                      /* second jmp, error */
          ret = 1;    
   }
   else
      ret = 1;

  lua_close(L);
  return ret;
}

Related Articles

Quote Of The Day