C API
- 云風Blog:Lua C API 的正確用法
- C讀取和調(diào)用Lua文件的庫:
lua.h, lauxlib.h, lualib.h - 包括:讀寫Lua全局變量的函數(shù)、調(diào)用Lua函數(shù)的函數(shù)、運行Lua代碼片段的函數(shù)、注冊C函數(shù)然后可以在Lua中被調(diào)用的函數(shù)
- C和Lua之間的數(shù)據(jù)交換,通過對棧上的值進行操作。棧的使用解決:Lua會自動進行垃圾回收,而C要求顯示的分配內(nèi)存單元;Lua中的動態(tài)類型和C的靜態(tài)類型。
- 壓入元素:
void lua_pushnil(lua_State *L); //插入空值
void lua_pushboolean(lua_State *L, int bool); //插入布爾值
void lua_pushnumber(lua_State *L, double n); //插入double
void lua_pushlstring(lua_State *L, const char* s, size_t length); //插入任意字符串
void lua_pushstring(lua_State *L, const char* s); //插入帶'\0'的字符串
-
查詢元素
1表示第一個被壓棧的元素;-1表示棧頂元素;當一個C函數(shù)返回后,Lua會清理他的棧,所以,有一個原則:永遠不要將指向Lua字符串的指針保存到訪問他們的外部函數(shù)中。
int lua_is*(lua_State *L, int index); //檢查一個元素是否指定類型(number,string,boolean,table)
int lua_toboolean(lua_State *L, int index);
double lua_tonumber(lua_State *L, int index);
const char* lua_tostring(lua_State *L, int index);
size_t lua_strlen(lua_State *L, int index);
- 其他堆棧操作
int lua_gettop(lua_State *L); //返回堆棧中的元素個數(shù)
void lua_settop(lua_State *L, int index); //設置棧頂為一個指定的值,多余值被拋棄,否則壓入nil值
void lua_pop(lua_State *L, int n); //lua_settop(L, -n - 1),從堆棧中彈出n個元素
void lua_pushvalue(lua_State *L, int index); //壓入堆棧上指定一個索引的拷貝到棧頂
void lua_remove(lua_State *L, int index); //移除指定索引位置的元素
void lua_insert(lua_State *L, int index); //移動棧頂元素到指定索引的位置
void lua_replace(lua_State *L, int index); //從棧頂中彈出元素并將其設置到指定索引位置
-
錯誤處理
應用程序中(調(diào)用Lua的C代碼)的錯誤處理:當Lua遇到類似內(nèi)存分配失敗的情況,大部分會拋出異常,調(diào)用panic函數(shù)退出應用;其余情況會忽略異常,在保護模式下運行代碼(Lua通過調(diào)用lua_pcall來運行)。
類庫(被Lua調(diào)用的C函數(shù))中的錯誤處理:C函數(shù)發(fā)現(xiàn)錯誤只要調(diào)用luaL_error函數(shù),清理所有在Lua中需要被清理的,然后和錯誤信息一起回到最初執(zhí)行lua_pcall的地方。 - 表操作
void lua_gettable(lua_State *L, int idx); //以棧頂元素為key值,獲取指定索引的表的值到棧頂
void lua_getfield(lua_State *L, int idx, const char *k); //獲取指定索引的表對應key的值到棧頂
void lua_getglobal(lua_State *L, const char *name); //等于lua_getfield(L, LUA_GLOBALSINDEX, (name))。獲取全局表的變量到棧頂
void lua_settable(lua_State *L, int idx); //以棧頂元素為value,棧頂下一元素為key,設置指定索引的表的值
void lua_setfield(lua_State *L, int idx, const char *k); //彈出棧頂元素,并設置為指定索引的表對應key的值
void lua_setglobal(lua_State *L, const char *name); //等于lua_setfield(L, LUA_GLOBALSINDEX, (name))。設置全局變量的值
void lua_rawgeti(lua_State *L, int idx, int n); //獲得idx索引的表以n為key的值
void lua_rawget(lua_State *L, int idx); //獲得idx索引的表以棧頂為key的值
void lua_rawseti(lua_State *L, int idx, int n); //設置idx索引的表以n為key的值
void lua_rawset(lua_State *L, int idx); //設置idx索引的表以棧頂下一個元素為key的值
-
調(diào)用函數(shù)
使用API調(diào)用函數(shù)的方法是很簡單的:首先,將被調(diào)用的函數(shù)入棧;第二,依次將所有參數(shù)入棧;第三,使用lua_pcall調(diào)用函數(shù);最后,從棧中獲取函數(shù)執(zhí)行返回的結果。
在將結果入棧之前,lua_pcall會將棧內(nèi)的函數(shù)和參數(shù)移除。如果lua_pcall運行時出現(xiàn)錯誤,lua_pcall會返回一個非0的結果,并將錯誤信息入棧(依然會先移除棧內(nèi)函數(shù)和參數(shù))
錯誤處理函數(shù)需要在被調(diào)用函數(shù)和參數(shù)之前入棧,參數(shù)errfunc為錯誤函數(shù)在棧中的索引。一般錯誤返回LUA_ERRRUN。內(nèi)存分配錯誤返回LUA_ERRMEM;在錯誤處理函數(shù)中出錯返回LUA_ERRERR,這兩種情況并不會調(diào)用錯誤處理函數(shù)。
int lua_pcall(lua_State *L, int nargs, int nresults, int errfunc); //調(diào)用棧頂函數(shù),指定參數(shù)格式nargs,返回結果個數(shù),nresults,和錯誤函數(shù)
-
調(diào)用C函數(shù)
Lua調(diào)用C函數(shù),使用棧進行交互,用來交互的棧不是全局變量,每一個函數(shù)都有他自己的私有棧,第一個參數(shù)總是在這個私有棧的index = 1的位置。
一個Lua庫實際上是一個定義了一系列Lua函數(shù)的chunk,并將這些函數(shù)保存在適當?shù)牡胤剑ǔW鳛?code>table 的域來保存。Lua的C庫就是這樣實現(xiàn)的。
在宿主程序中調(diào)用C函數(shù)
//注冊被Lua調(diào)用的C函數(shù)的格式,以a+b為例
static int func(lua_State *L)
{
//從棧中獲取函數(shù)參數(shù)并檢查合法性,1表示Lua調(diào)用時的第一個參數(shù),以此類推
double arg1 = luaL_checknumber(L, 1);
double arg2 = luaL_checknumber(L, 2);
//將函數(shù)結果入棧,如果有多個返回值,可以多次入棧
lua_pushnumber(L, arg1 + arg2);
//返回值表示該函數(shù)的返回值的數(shù)量
return 1;
}
int main()
{
lua_State* L = luaL_newstate();
luaL_openlibs(L);
//注冊被Lua調(diào)用的C函數(shù),參數(shù)"add"表示Lua調(diào)用時使用的全局函數(shù)名,func為被調(diào)用的C函數(shù)
lua_register(L, "add", func);
luaL_dostring("print(add(1, 2))");
lua_close(L);
return 0;
}
C函數(shù)庫
//注冊被Lua調(diào)用的C函數(shù)的格式,以a+b為例,函數(shù)必須以C的形式被導出
extern "C" int func(lua_State *L)
{
//從棧中獲取函數(shù)參數(shù)并檢查合法性,1表示Lua調(diào)用時的第一個參數(shù),以此類推
double arg1 = luaL_checknumber(L, 1);
double arg2 = luaL_checknumber(L, 2);
//將函數(shù)結果入棧,如果有多個返回值,可以多次入棧
lua_pushnumber(L, arg1 + arg2);
//返回值表示該函數(shù)的返回值的數(shù)量
return 1;
}
static luaL_Reg mylibs[] =
{
{"add", func},
{NULLL, NULL},
};
//注冊mylibs庫,Lua中使用該庫的用法:testlibs.add(1, 2)
extern "C" __declspec(dllexport)
int luaopen_testlibs(lua_State* L)
{
luaL_newlib(L, mylibs);
return 1;
}
-
字符串處理
當C函數(shù)接受一個來自lua的字符串作為參數(shù)時,有兩個規(guī)則必須遵守:當字符串正在被訪問的時候不要將其出棧;永遠不要修改字符串。
當C函數(shù)需要創(chuàng)建一個字符串返回給lua的時候,C代碼負責緩沖區(qū)的分配和釋放,負責處理緩沖溢出等情況。
void lua_concat(lua_State *L, int n); //連接棧頂開始的n個字符串,彈出這n個字符串并壓棧結果
const char *lua_pushfstring(lua_State *L, const char *fmt, ...); //根據(jù)格式串fmt的要求創(chuàng)建一個新的字符串。
-
注冊表
使用LUA_REGISTRYINDEX索引來保存注冊表中的Lua值。任何C庫都可以在這張表里保存數(shù)據(jù), 為了防止沖突,可以使用保護庫名前綴的名字作為key值。注冊表中的整數(shù)key用于應用機制luaL_ref。 -
閉包
一個C函數(shù)和它的upvalues的組合稱為閉包。upvalues為函數(shù)能訪問的外部局部變量。
static int counter(lua_State *L)
{
double val = lua_tonumber(L, lua_upvalveindex(1));
lua_pushnumber(L, ++val);
lua_pushvalue(L, -1);
lua_replace(L, lua_upvalueindex(1));
return 1;
}
int newCounter(lua_State *L)
{
lua_pushnumber(L, 0);
//第二個參數(shù)為基本函數(shù),第三個參數(shù)是upvalues的個數(shù)
lua_pushcclosure(L, &counter, 1);
return 1;
}
- Userdata
void *lua_newuserdata(lua_State *L, size_t size); //按照指定size的大小分配一段內(nèi)存放入棧內(nèi),并返回這個地址
- Metatable
int luaL_newmetatable(lua_State *L, const char* tname); //棧頂創(chuàng)建metatable,建立表和registry中類型名的雙向關系(分別以tname為表的key和以表為tname的key)
void luaL_getmetatable(lua_State *L, const char* tname); //獲取registry中tname對應的表
void* luaL_checkudata(lua_State *L, int index, const char* tname); //檢查指定索引的元素是否為帶有給定tname的metatable的useratum,如果是,返回useratum的地址,否則返回NULL