Step By Step(Lua-C API簡介)

來源:互聯網
上載者:User

    Lua是一種嵌入式指令碼語言,即Lua不是可以單獨啟動並執行程式,在實際應用中,主要存在兩種應用形式。第一種形式是,C/C++作為主程式,調用Lua代碼,此時可以將Lua看做“可擴充的語言”,我們將這種應用稱為“應用程式代碼”。第二種形式是Lua具有控制權,而C/C++代碼則作為Lua的“庫代碼”。在這兩種形式中,都是通過Lua提供的C API完成兩種語言之間的通訊的。

    1. 基礎知識:
    C API是一組能使C/C++代碼與Lua互動的函數。其中包括讀寫Lua全域變數、調用Lua函數、運行一段Lua代碼,以及註冊C函數以供Lua代碼調用等。這裡先給出一個簡單的範例程式碼:

 1 #include <stdio.h>
2 #include <string.h>
3 #include <lua.hpp>
4 #include <lauxlib.h>
5 #include <lualib.h>
6
7 int main(void)
8 {
9 const char* buff = "print(\"hello\")";
10 int error;
11 lua_State* L = luaL_newstate();
12 luaL_openlibs(L);
13
14 error = luaL_loadbuffer(L,buff,strlen(buff),"line") || lua_pcall(L,0,0,0);
15 int s = lua_gettop(L);
16 if (error) {
17 fprintf(stderr,"%s",lua_tostring(L,-1));
18 lua_pop(L,1);
19 }
20 lua_close(L);
21 return 0;
22 }

    下面是針對以上代碼給出的具體解釋:
    1). 上面的代碼是基於我的C++工程,而非C工程,因此包含的標頭檔是lua.hpp,如果是C工程,可以直接包含lua.h。
    2). Lua庫中沒有定義任何全域變數,而是將所有的狀態都儲存在動態結構lua_State中,後面所有的C API都需要該指標作為第一個參數。
    3). luaL_openlibs函數是用於開啟Lua中的所有標準庫,如io庫、string庫等。
    4). luaL_loadbuffer編譯了buff中的Lua代碼,如果沒有錯誤,則返回0,同時將編譯後的程式塊壓入虛擬棧中。
    5). lua_pcall函數會將程式塊從棧中彈出,並在保護模式下運行該程式塊。執行成功返回0,否則將錯誤資訊壓入棧中。
    6). lua_tostring函數中的-1,表示棧頂的索引值,棧底的索引值為1,以此類推。該函數將返回棧頂的錯誤資訊,但是不會將其從棧中彈出。
    7). lua_pop是一個宏,用於從虛擬棧中彈出指定數量的元素,這裡的1表示僅彈出棧頂的元素。
    8). lua_close用於釋放狀態指標所引用的資源。

    2. 棧:
    在Lua和C語言之間進行資料交換時,由於兩種語言之間有著較大的差異,比如Lua是動態類型,C語言是靜態類型,Lua是自動記憶體管理,而C語言則是手動記憶體管理。為瞭解決這些問題,Lua的設計者使用了虛擬棧作為二者之間資料互動的介質。在C/C++程式中,如果要擷取Lua的值,只需調用Lua的C API函數,Lua就會將指定的值壓入棧中。要將一個值傳給Lua時,需要先將該值壓入棧,然後調用Lua的C API,Lua就會擷取該值並將其從棧中彈出。為了可以將不同類型的值壓入棧,以及從棧中取出不同類型的值,Lua為每種類型均設定了一個特定函數。
    1). 壓入元素:
    Lua針對每種C類型,都有一個C API函數與之對應,如:
    void lua_pushnil(lua_State* L);  --nil值
    void lua_pushboolean(lua_State* L, int b); --布爾值
    void lua_pushnumber(lua_State* L, lua_Number n); --浮點數
    void lua_pushinteger(lua_State* L, lua_Integer n);  --整型
    void lua_pushlstring(lua_State* L, const char* s, size_t len); --指定長度的記憶體資料
    void lua_pushstring(lua_State* L, const char* s);  --以零結尾的字串,其長度可由strlen得出。
    對於字串資料,Lua不會持有他們的指標,而是調用在API時產生一個內部副本,因此,即使在這些函數返回後立刻釋放或修改這些字串指標,也不會有任何問題。
    在向棧中壓入資料時,可以通過調用下面的函數判斷是否有足夠的棧空間可用,一般而言,Lua會預留20個槽位,對於普通應用來說已經足夠了,除非是遇到有很多參數的函數。
    int lua_checkstack(lua_State* L, int extra) --期望得到extra數量的空閑槽位,如果不能擴充並獲得,返回false。
    
    2). 查詢元素:
    API使用“索引”來引用棧中的元素,第一個壓入棧的為1,第二個為2,依此類推。我們也可以使用負數作為索引值,其中-1表示為棧頂元素,-2為棧頂下面的元素,同樣依此類推。
    Lua提供了一組特定的函數用於檢查返回元素的類型,如:
    int lua_isboolean (lua_State *L, int index);
    int lua_iscfunction (lua_State *L, int index);
    int lua_isfunction (lua_State *L, int index);
    int lua_isnil (lua_State *L, int index);
    int lua_islightuserdata (lua_State *L, int index);
    int lua_isnumber (lua_State *L, int index);
    int lua_isstring (lua_State *L, int index);
    int lua_istable (lua_State *L, int index);
    int lua_isuserdata (lua_State *L, int index);
    以上函數,成功返回1,否則返回0。需要特別指出的是,對於lua_isnumber而言,不會檢查值是否為數字類型,而是檢查值是否能轉換為數字類型。
    Lua還提供了一個函數lua_type,用於擷取元素的類型,函數原型如下:
    int lua_type (lua_State *L, int index);
    該函數的傳回值為一組常量值,分別是:LUA_TNIL、LUA_TNUMBER、LUA_TBOOLEAN、LUA_TSTRING、LUA_TTABLE、LUA_TFUNCTION、LUA_TUSERDATA、LUA_TTHREAD和LUA_TLIGHTUSERDATA。這些常量通常用於switch語句中。
    除了上述函數之外,Lua還提供了一群組轉換函數,如:
    int lua_toboolean (lua_State *L, int index);
    lua_CFunction lua_tocfunction (lua_State *L, int index);
    lua_Integer lua_tointeger (lua_State *L, int index);    
    const char *lua_tolstring (lua_State *L, int index, size_t *len);
    lua_Number lua_tonumber (lua_State *L, int index);
    const void *lua_topointer (lua_State *L, int index);
    const char *lua_tostring (lua_State *L, int index);
    void *lua_touserdata (lua_State *L, int index);
    --string類型返回字串長度,table類型返回操作符'#'等同的結果,userdata類型返回分配的記憶體塊長度。
    size_t lua_objlen (lua_State *L, int index); 
    對於上述函數,如果調用失敗,lua_toboolean、lua_tonumber、lua_tointeger和lua_objlen均返回0,而其他函數則返回NULL。在很多時候0不是一個很有效用於判斷錯誤的值,但是ANSI C沒有提供其他可以表示錯誤的值。因此對於這些函數,在有些情況下需要先使用lua_is*系列函數判斷是否類型正確,而對於剩下的函數,則可以直接通過判斷傳回值是否為NULL即可。
    對於lua_tolstring函數返回的指向內部字串的指標,在該索引指向的元素被彈出之後,將無法保證仍然有效。該函數返回的字串末尾均會有一個尾部0。
    下面將給出一個工具函數,可用於示範上面提到的部分函數,如:

 1 static void stackDump(lua_State* L) 
2 {
3 int top = lua_gettop(L);
4 for (int i = 1; i <= top; ++i) {
5 int t = lua_type(L,i);
6 switch(t) {
7 case LUA_TSTRING:
8 printf("'%s'",lua_tostring(L,i));
9 break;
10 case LUA_TBOOLEAN:
11 printf(lua_toboolean(L,i) ? "true" : "false");
12 break;
13 case LUA_TNUMBER:
14 printf("%g",lua_tonumber(L,i));
15 break;
16 default:
17 printf("%s",lua_typename(L,t));
18 break;
19 }
20 printf("");
21 }
22 printf("\n");
23 }

    3). 其它棧操作函數:
    除了上面給出的資料交換函數之外,Lua的C API還提供了一組用於操作虛擬棧的普通函數,如:
    int lua_gettop(lua_State* L); --返回棧中元素的個數。
    void lua_settop(lua_State* L, int index); --將棧頂設定為指定的索引值。
    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); --彈出棧頂元素,並將該值設定到指定索引上。
    Lua還提供了一個宏用於彈出指定數量的元素:#define lua_pop(L,n)  lua_settop(L, -(n) - 1)    
    見如下範例程式碼:

 1 int main()
2 {
3 lua_State* L = luaL_newstate();
4 lua_pushboolean(L,1);
5 lua_pushnumber(L,10);
6 lua_pushnil(L);
7 lua_pushstring(L,"hello");
8 stackDump(L); //true 10 nil 'hello'
9
10 lua_pushvalue(L,-4);
11 stackDump(L); //true 10 nil 'hello' true
12
13 lua_replace(L,3);
14 stackDump(L); //true 10 true 'hello'
15
16 lua_settop(L,6);
17 stackDump(L); //true 10 true 'hello' nil nil
18
19 lua_remove(L,-3);
20 stackDump(L); //true 10 true nil nil
21
22 lua_settop(L,-5);
23 stackDump(L); //true
24
25 lua_close(L);
26 return 0;
27 }

    3. C API中的錯誤處理:
    1). C程式調用Lua代碼的錯誤處理:
    通常情況下,應用程式代碼是以“無保護”模式啟動並執行。因此,當Lua發現“記憶體不足”這類錯誤時,只能通過調用“緊急”函數來通知C語言程式,之後在結束應用程式。使用者可通過lua_atpanic來設定自己的“緊急”函數。如果希望應用程式代碼在發生Lua錯誤時不會退出,可通過調用lua_pcall函數以保護模式運行Lua代碼。這樣再發生記憶體錯誤時,lua_pcall會返回一個錯誤碼,並將解譯器重設為一致的狀態。如果要保護與Lua的C代碼,可以使用lua_cpall函數,它將接受一個C函數作為參數,然後調用這個C函數。
    
    2). Lua調用C程式:
    通常而言,當一個被Lua調用的C函數檢測到錯誤時,它就應該調用lua_error,該函數會清理Lua中所有需要清理的資源,然後跳回發起執行的那個lua_pcall,並附上一條錯誤資訊。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.