前言ios
對於Lua的基礎總結總算告一段落了,從這篇博文開始,咱們才真正的進入Lua的世界,一個無聊而又有趣的世界。來吧。程序員
Lua語言是一種嵌入式語言,它自己的威力有限;當Lua碰見了C,那它就展現了它的強大威力。C和Lua是能夠相互調用的。第一種狀況是,C語言擁有控制權,Lua是一個庫,這種形式中的C代碼稱爲「應用程序代碼」;第二種狀況是,Lua擁有控制權,C語言是一個庫,這個時候C代碼就是「庫代碼」。「應用程序代碼」和「庫代碼」都使用一樣的API來與Lua通訊,這些API就稱爲C API。安全
C API是一組能使C代碼與Lua交互的函數,包括不少對Lua代碼的操做。如何操做,操做什麼,咱們的文章我都會一一總結。C API是很是靈活而強大的。爲了表示它的NB之處,不先來一段小的DEMO程序展現一下,怎麼可以行呢?函數
代碼以下:lua
#include <iostream>
#include <string.h>
extern "C"
{
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
}
int main()
{
char buff[256] = {0};
int error;
lua_State *L = luaL_newstate(); // 打開Lua
luaL_openlibs(L); // 打開標準庫
while (fgets(buff, sizeof(buff), stdin) != NULL)
{
error = luaL_loadbuffer(L, buff, strlen(buff), "line")
|| lua_pcall(L, 0, 0, 0);
if (error)
{
fprintf(stderr, "%s", lua_tostring(L, -1));
lua_pop(L, 1); // 從棧中彈出錯誤消息
}
}
lua_close(L);
return 0;
}
spa
若是你沒有接觸過C API,對於上面這段代碼,你確定不會明白它是幹什麼的。什麼也不說,你運行一下吧。而後輸入Lua語句,看看運行結果。.net
先對上述代碼引入的幾個頭文件進行解釋一下:指針
頭文件lua.h定義了Lua提供的基礎函數,包括建立Lua環境、調用Lua函數、讀寫Lua環境中全局變量,以及註冊供Lua調用的新函數等等;orm
頭文件lauxlib.h定義了輔助庫提供的輔助函數,它的全部定義都以LuaL_開頭。輔助庫是一個使用lua.h中API編寫出的一個較高的抽象層。Lua的全部標準庫編寫都用到了輔助庫;輔助庫主要用來解決實際的問題。輔助庫並無直接訪問Lua的內部,它都是用官方的基礎API來完成全部工做的;htm
頭文件lualib.h定義了打開標準庫的函數。Lua庫中沒有定義任何全局變量。它將全部的狀態都保存在動態結構lua_State中,全部的C API都要求傳入一個指向該結構的指針。luaL_newstate函數用於建立一個新環境或狀態。當luaL_newstate建立一個新的環境時,新的環境中並無包含預約義的函數(eg.print)。爲了使Lua保持靈活,小巧,全部的標準庫都被組織到了不一樣的包中。當咱們須要使用哪一個標準庫時,就能夠調用lualib.h中定義的函數來打開對應的標準庫;而輔助函數luaL_openlibs則能夠打開全部的標準庫。
頭文件說完了,若是對代碼中的extern 「C」不懂的同窗,請看這裏。而後,就沒有而後了,而後我就先不解釋了,等我將後面的內容總結完,再回過頭來看,你會明白的更完全。點擊這裏去下載完整項目工程。
棧
Lua和C語言通訊的主要方法是一個無處不在的虛擬棧。幾乎全部的API調用都會操做這個棧上的值;全部的數據交換,不管是Lua到C語言或C語言到Lua都經過這個棧來完成。棧能夠解決Lua和C語言之間存在的兩大差別,第一種差別是Lua使用垃圾收集,而C語言要求顯式地釋放內存;第二種是Lua使用動態類型,而C語言使用靜態類型。
爲了屏蔽C和Lua之間的差別性,讓彼此之間的交互變的一般,便出現了這個虛擬棧。棧中的每一個元素都能保存任何類型的Lua值,當在C代碼中要獲取Lua中的一個值時,只需調用一個Lua API函數,Lua就會將指定值壓入棧中;要將一個值傳給Lua時,須要先將這個值壓入棧,而後調用Lua API,Lua就會得到該值並將其從棧中彈出。爲了將C類型的值壓入棧,或者從棧中獲取不一樣類型的值,就須要爲每種類型定義一個特定的函數。是的,咱們的確是這麼幹的。
Lua嚴格地按照LIFO規範來操做這個棧。但調用Lua時,Lua只會改變棧的頂部。不過,C代碼則有更大的自由度,它能夠檢索棧中間的元素,甚至在棧的任意位置插入或刪除元素。
壓入棧
對於每種能夠呈如今Lua中的C類型,API都有一個對應的壓入函數,我這裏把它們都列出來:
複製代碼代碼以下:
void lua_pushnil(lua_State *L);
void lua_pushboolean(lua_State *L, int bool);
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);
上面的函數很是簡單,從命名就能知道它們的含義。稍後提供詳細的實例代碼。因爲這個棧並非無限大的,當向棧中壓入一個元素時,應該確保棧中具備足夠的空間。當Lua啓動時,或Lua調用C語言時,棧中至少會有20個空閒的槽。這些空間通常狀況下是足夠的,全部咱們通常是不用管的,但老是會有特殊狀況的,若是調用一個具備不少參數的函數,就須要調用lua_checkstack來檢查棧中是否有足夠的空間。
查詢元素
API 使用索引來棧中的元素。第一個壓入棧中的元素索引爲1,第二個壓入的元素因此爲2,以此類推,直到棧頂。咱們也能夠用棧頂做爲參照物,使用負數來訪問棧中的元素,此時,-1表示棧頂元素,-2表示棧頂下面的元素,以此類推。有的狀況適合使用正數索引,而有的狀況下適合使用負數索引,咱們能夠根據實際需求,靈活變通。
爲了檢查一個元素是否爲特定的類型,API提供了一系列的函數lua_is*,其中*能夠是任意Lua類型。這些函數有lua_isnumber、lua_isstring和lua_istable等,全部這些函數都有一樣的原型:
複製代碼代碼以下:
int lua_is*(lua_State *L, int index);
實際上,lua_isnumber不會檢查值是否爲數字類型,而是檢查值是否能轉換爲數字類型。lua_isstring也具備一樣的行爲,這樣就出現一種情況,對於能轉換成string的值,lua_isstring老是返回真,因此lua_is*這類函數在使用的時候,並非很是的方便,因此,就出現了一個lua_type函數,它會返回棧中元素的類型,每種類型都對應一個常亮,這些常亮定義在頭文件lua.h中,它們是:
代碼以下:
/*
** basic types
*/
#define LUA_TNONE (-1)
#define LUA_TNIL 0
#define LUA_TBOOLEAN 1
#define LUA_TLIGHTUSERDATA 2
#define LUA_TNUMBER 3
#define LUA_TSTRING 4
#define LUA_TTABLE 5
#define LUA_TFUNCTION 6
#define LUA_TUSERDATA 7
#define LUA_TTHREAD 8
若是要檢查一個元素是否爲真正的字符串或數字(無需轉換),也可使用這個函數。
取值
咱們通常使用lua_to*函數用於從棧中獲取一個值,有如下經常使用的取值函數:
代碼以下:
lua_Number lua_tonumber (lua_State *L, int idx);
lua_Integer lua_tointeger (lua_State *L, int idx);
int lua_toboolean (lua_State *L, int idx);
const char *lua_tolstring (lua_State *L, int idx, size_t *len);
size_t lua_objlen (lua_State *L, int idx);
lua_CFunction lua_tocfunction (lua_State *L, int idx);
void *lua_touserdata (lua_State *L, int idx);
lua_State *lua_tothread (lua_State *L, int idx);
const void *lua_topointer (lua_State *L, int idx);
若是指定的元素不具備正確的類型,調用這些函數也不會有問題。在這種狀況下,lua_toboolean、lua_tonumber、lua_tointeger和lua_objlen會返回0,而其它函數會返回NULL。lua_tolstring函數會返回一個指向內部字符串副本的指針,並將字符串的長度存入最後一個參數len中。這個內部副本不能修改,返回類型中的const也說明了這點。Lua保證只要這個對應的字符串還在棧中,那麼這個指針就是有效的。當Lua調用的一個C函數返回時,Lua就會清空它的棧。這就有一條很是重要的規則:
代碼以下:
***不要在C函數以外使用在C函數內得到的指向Lua字符串的指針***
全部lua_tolstring返回的字符串在其末尾都會有一個額外的零,不過這些字符串中間也可能有零,字符串的長度經過第三個參數len返回,這纔是真正的字符串長度。
lua_objlen函數能夠返回一個對象的「長度」。對於字符串和table,這個值就是長度操做符「#」的結果。這個函數還可用於獲取一個「徹底userdata」的大小,關於userdata,後面還會單獨總結。
其它棧操做
除了在C語言和棧之間交換數據的函數外,API還提供瞭如下這些用於普通棧操做的函數:
複製代碼代碼以下:
/*
** basic stack manipulation
*/
int lua_gettop (lua_State *L);
void lua_settop (lua_State *L, int idx);
void lua_pushvalue (lua_State *L, int idx);
void lua_remove (lua_State *L, int idx);
void lua_insert (lua_State *L, int idx);
void lua_replace (lua_State *L, int idx);
如今就來簡單的說說這幾個函數,lua_gettop函數返回棧中元素的個數,也能夠說是棧頂元素的索引。lua_settop將棧頂設置爲一個指定的位置,即修改棧中元素的數量,若是以前的棧頂比新設置的更高,那麼高出來的這些元素會被丟棄;反之,會向棧中壓入nil來補足大小;好比,調用如下語句就能清空棧:
複製代碼代碼以下:
lua_settop(L, 0);
也可使用負數索引來使用lua_settop。lua_pushvalue函數會將指定索引上值得副本壓入棧。lua_remove刪除指定索引上的元素,並將該位置之上的全部元素下移以填補空缺。lua_insert會上移指定位置之上的全部元素以開闢一個槽空間,而後將棧頂元素移到該位置。lua_replace彈出棧頂的值,並將該值設置到指定索引上,但它不會移動任何東西,只是替換了指定索引的值。說了這麼多,總結了這麼多,不來點真槍實幹的,老是覺的很虛,上代碼。點擊這裏去下載本篇博文中全部的代碼工程吧。
C API出錯了怎麼辦?
沒有十全十美,沒有任何bug的程序的。是的,再NB的人寫的程序,也可能出現問題,有些問題不是咱們控制範圍以內的。既然咱們沒法控制問題的出現,可是咱們對問題出現之後的行爲進行處理,好比:出現問題了,彈出一個友好的message,這聽起來仍是不錯的,不少程序都是這麼幹的。好吧,夥計,若是C API出錯了怎麼辦呢?
Lua中全部的結構都是動態的,它們會根據須要來增加,或者縮小。是的,增加縮小,就涉及到內存的開闢與釋放,這有可能會出錯的,雖然我知道這個機率是很低的,可是對於程序員來講,對於任何可能出現問題的地方都要進行處理。這裏有兩種狀況:
1.C調用Lua代碼;
2.Lua代碼調用C。
不是全部的API函數都會拋出異常。函數luaL_newstate、lua_load、lua_pcall和lua_close都是安全的。在第一種狀況下,通常都是使用lua_pcall來運行Lua代碼,因爲lua_pcall是在保護的狀況下運行lua代碼,若是發生了內存分配錯誤,lua_pcall會返回一個錯誤代碼,並將解釋器封固在一致的狀態;若是要保護那些與Lua交互的C代碼,可使用lua_cpcall,這個函數相似於lua_pcall。
對於Lua調用C,當將新的C函數加入Lua時,可能會破壞內存的結構。當咱們爲Lua編寫庫函數時(Lua調用C的函數),只有一種標準的錯誤處理方法。當一個C函數檢測到一個錯誤時,它就應該調用lua_error,lua_error函數會清理Lua中全部須要清理的東西,而後跳轉回發起執行的那個lua_pcall,並附上一條錯誤消息。在後面的博文中,會有這方面的代碼實例的。