雲風的博文《Lua C API 的正確用法》讀後總結
阿新 • • 發佈:2019-01-23
雲風的博文《Lua C API 的正確用法》(http://blog.codingnow.com/2015/05/lua_c_api.html)
該文章是一年前寫的,不好意思在原文下面寫心得體會了,就把想說的寫在這裡。
1,在你的程式中嵌入lua時,最好使用由你的編譯器編譯lua原始碼得到的庫檔案(lua.lib)。
這是因為,在lua的異常處理機制裡面會使用一些巨集,在不同的編譯環境下這些巨集有不同的定義,例如:
如果你的程式是C++編寫的,但是連結的卻是在C環境下編譯出來的lua.lib,那麼表面上工作正常,但是一涉及到異常處理,就會出現很多未知的問題。#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
2,在你的程式碼中使用lua API時,由於lua API有丟擲異常的可能,會導致“丟擲異常處”的後面的程式碼不能執行到。例如:
如果在執行lua_tostring()時丟擲了異常,那麼後面的 “delete pObj;" 就不能執行到,造成記憶體洩漏。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_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 */
...
}