1. 程式人生 > >雲風的博文《Lua C API 的正確用法》讀後總結

雲風的博文《Lua C API 的正確用法》讀後總結

雲風的博文《Lua C API 的正確用法》(http://blog.codingnow.com/2015/05/lua_c_api.html)

該文章是一年前寫的,不好意思在原文下面寫心得體會了,就把想說的寫在這裡。

1,在你的程式中嵌入lua時,最好使用由你的編譯器編譯lua原始碼得到的庫檔案(lua.lib)。

這是因為,在lua的異常處理機制裡面會使用一些巨集,在不同的編譯環境下這些巨集有不同的定義,例如:

#if defined(__cplusplus)
/* C++ exceptions */
#define LUAI_THROW(L,c)	throw(c)
#define LUAI_TRY(L,c,a)	try { a } catch(...) \
	{ if ((c)->status == 0) (c)->status = -1; }
#define luai_jmpbuf	int  /* dummy variable */

#elif defined(LUA_USE_ULONGJMP)
/* in Unix, try _longjmp/_setjmp (more efficient) */
#define LUAI_THROW(L,c)	_longjmp((c)->b, 1)
#define LUAI_TRY(L,c,a)	if (_setjmp((c)->b) == 0) { a }
#define luai_jmpbuf	jmp_buf

#else
/* default handling with long jumps */
#define LUAI_THROW(L,c)	longjmp((c)->b, 1)
#define LUAI_TRY(L,c,a)	if (setjmp((c)->b) == 0) { a }
#define luai_jmpbuf	jmp_buf

#endif
如果你的程式是C++編寫的,但是連結的卻是在C環境下編譯出來的lua.lib,那麼表面上工作正常,但是一涉及到異常處理,就會出現很多未知的問題。


2,在你的程式碼中使用lua API時,由於lua API有丟擲異常的可能,會導致“丟擲異常處”的後面的程式碼不能執行到。例如:

struct foobar
{
  const char *a;
  const char *b;
}

foobar* pObj = new foobar;
pObj->a = lua_tostring(L, 1);
pObj->b = lua_tostring(L, 2);
...
delete pObj;
如果在執行lua_tostring()時丟擲了異常,那麼後面的 “delete pObj;" 就不能執行到,造成記憶體洩漏。

對於這樣的臨時記憶體塊,有一個解決辦法是使用 lua_newuserdata 來申請一段由lua維護的記憶體塊,它會被GC操作回收掉。例如:

foobar* pObj = lua_newuserdata(L, sizeof(foobar));
pObj->a = lua_tostring(L, 1);
pObj->b = lua_tostring(L, 2);
上面的程式碼有另外一個潛在問題,pObj物件沒有初始化,一旦執行lua_tostring()時丟擲了異常,就會使得 pObj->a 或者 pObj->b 的值是未初始化的,不管是在宿主程式碼中訪問pObj物件,還是在lua指令碼中訪問pObj物件,都可能帶來未知的問題。正確的寫法是這樣:
foobar* pObj = lua_newuserdata(L, sizeof(foobar));
pObj->a = NULL; //首先做初始化
pObj->b = NULL;
pObj->a = lua_tostring(L, 1);
pObj->b = lua_tostring(L, 2);

3,lua API函式可能會丟擲異常,這些異常應該由 lua_pcall 或者 lua_resume 來捕獲,所以要確保lua API函式的呼叫處於某次 lua_pcall 或者 lua_resume 中。

下面是最常見的建立lua環境的程式碼,但是這段程式碼就是有風險的,因為 luaL_openlibs(L) 有可能丟擲異常,但是沒有捕獲異常:

lua_State *L = luaL_newstate();
if (L)
{
    luaL_openlibs(L);
}
正確的做法是,把你的程式碼寫到一個 lua_CFunction 中,然後用 lua_pcall 來呼叫它。而 lua_CFunction 中需要使用的引數,則應該用 void * 通過 lua_pushlightuserdata 來傳遞。

lua官方的直譯器的程式原始碼,就為我們示範了這種正確的做法:

//輔助函式
static int pmain (lua_State *L)
{
  int argc = (int)lua_tointeger(L, 1);
  char **argv = (char **)lua_touserdata(L, 2);
  ...
  luaL_openlibs(L);  /* open standard libraries */
  ...
}
//主函式
int main (int argc, char **argv)
{
  int status, result;
  lua_State *L = luaL_newstate();  /* create state */
  lua_pushcfunction(L, &pmain);  /* to call 'pmain' in protected mode */
  lua_pushinteger(L, argc);  /* 1st argument */
  lua_pushlightuserdata(L, argv); /* 2nd argument */
  status = lua_pcall(L, 2, 1, 0);  /* do the call */
  result = lua_toboolean(L, -1);  /* get result */
  ...
}