lua和C交互框架
1、錯誤(err_return)的宏定義
#define err_return(num,fmt,args)? /
??? do
??? {
??????? printf("[%s:%d]"fmt"/n",__FILE__,__LINE__,##args);return(num);
??? } while(0)
-1? 是return的返回值,表示有錯誤;
fmt 是一個字符串,用于打印錯誤信息;
args表示參數的個數;--這個有疑問,__FILE__,__LINE__,##args 是什么意思?
另外: while(0)是什么意思,什么條件下可以退出?
具體調用實例:err_return(-1,"luaL_newstat() failed",1);
?
補充解釋:
//__FILE__??? 進行編譯的源文件名?
//__LINE__??? 文件當前有的行號(注意:是"當前")?
//__DATE__??? 文件被編譯的日期?
//__TIME__??? 文件被編譯的時間?
##是字符拼接
像這樣?
在#define中,標準只定義了#和##兩種操作。#用來把參數轉換成字符串,##則用來連接兩個前后兩個參數,把它們變成一個字符串。 eg.ab##cd 就是abcd, m##1 就是m1;
另外: while(0)是什么意思,什么條件下可以退出?
直接退出,0為false
0是false所以只執行一次do就退出了
while()循環進入的條件是,條件為TRUE,因為0是FALSE,所以循環一次就退出;
do~while() 循環執行一次do里面的內容然后退出,如果直接是while()循環,則不會進入循環體;
?
2、在lua中調用C函數:
(1)C中寫好lua要調用的函數
//lua中要調用的c函數定義,實現加法
int csum(lua_State* l)??? //定義函數,函數參數是lua_State* l,這也是每個lua要調用的C函數的寫法;
{
??? int a = lua_tointeger(l,1) ;? //lua_tointeger()獲得整型返回值,一般是把lua傳進去的數值(參數或者全局變量)轉換成整形;
??? int b = lua_tointeger(l,2) ;? //這里是指第二個參數,被轉化成整形;
??? lua_pushinteger(l,a+b) ;????? //lua_pushinteger()這里是把返回值(a+b)壓入棧
??? return 1 ;??????????????????? //返回值的數量;
}
可見,lua調用C函數,首先是要寫好C中的函數的,首先把函數參數轉化成想要的類型,然后把返回值表達式壓入棧;
(2)C中寫好注冊函數,一般是下面的兩個:
??? lua_pushcfunction(l,csum) ;???????? //注冊在lua中使用的c函數
??? lua_setglobal(l,"csum") ;?????????? //綁定到lua中的名字csum
也可以使用:
??? lua_register(l,"csum",csum);
因為:
??? #define??? lua_register(L,n,f)??? (lua_pushcfunction(L, (f)), lua_setglobal(L, (n)))
(3)C調用lua函數
這里分步進行分析:
main函數里面的事情:
1、/* 初始化lua */
lua_State* l = lua_open();
2、/* 載入Lua基本庫 */
luaL_openlibs(L);
另:
??? lua_State * l = luaL_newstate() ;??? //創建lua運行環境
??? if ( l == NULL )?
?{
??????????? err_return(-1,"luaL_newstat() failed",1);
??????? }
1、2 和 創建lua運行環境的不同;
3、/* 載入腳本 */
luaL_dofile(L, "add.lua");
luaL_dofile 相當于:(luaL_loadfile(L, filename) || lua_pcall(L, 0, LUA_MULTRET, 0))
當然使用luaL_loadfile 要注意調用 lua_pcall 函數 清空堆棧;
4、/* 調用Lua函數 */
這里就可以調用之前定義好的功能函數;具體功能封裝到C的函數中;
5、/* 顯示結果 */
printf( "The sum is %d/n", sum );
6、/* 清除Lua */
lua_close(L);
補充上面第四步,分析封裝到C函數中的lua函數調用方法:
int luaadd ( int x, int y )
{
?int sum;??????????????????? //定義一個變量作為返回值;
?/* 通過名字得到Lua函數 */
?lua_getglobal(L, "add");??? //使用lua_getglobal得到全局變量;
?/* 第一個參數 */
?lua_pushnumber(L, x);?????? //將第一個參數壓入棧中;
?/* 第二個參數 */????????????
?lua_pushnumber(L, y);?????? //第二個參數壓入棧中;
? /* 調用函數,告知有兩個參數,一個返回值 */
?lua_pcall(L, 2, 1,0);????? //調用此函數會自動將參數彈出棧,只保留返回值;
?/* 得到結果 */
?sum = (int)lua_tointeger(L, -1);
?lua_pop(L, 1);????????????? //將返回值從棧中清除;???
?return sum;
}
(4)C調用lua全局變量
main函數里面的初始化一樣;
另外調用全局變量用的也是lua_getglobal()方法;
比如:lua_getglobal(l,"width");??? //獲取lua中定義的變量
各種交互具體代碼示例:
extern "C"
{
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
}
#define err_exit(num,fmt,args)? /
??? do{printf("[%s:%d]"fmt"/n",__FILE__,__LINE__,##args);exit(num);} while(0)
#define err_return(num,fmt,args)? /
??? do{printf("[%s:%d]"fmt"/n",__FILE__,__LINE__,##args);return(num);} while(0)
//lua中調用的c函數定義,實現加法
int csum(lua_State* l)
{
??? int a = lua_tointeger(l,1) ;
??? int b = lua_tointeger(l,2) ;
??? lua_pushinteger(l,a+b) ;
??? return 1 ;
}
int main(int argc,char** argv)
{
??? lua_State * l = luaL_newstate() ;??????? //創建lua運行環境
??? if ( l == NULL )?
?{
??err_return(-1,"luaL_newstat() failed",1);
??? }
?//int ret = 0 ;
??? int ret = luaL_loadfile(l,"func.lua") ;????? //加載lua腳本文件
??? if ( ret != 0 )?
??err_return(-1,"luaL_loadfile failed",1) ;
??? ret = lua_pcall(l,0,0,0) ;
??? if ( ret != 0 )
??err_return(-1,"lua_pcall failed:%s",lua_tostring(l,-1)) ;
??? lua_getglobal(l,"width");????????????? //獲取lua中定義的變量
??? lua_getglobal(l,"height");???????????? //如果改變了字符串的值,則不會打印出正確的結果,默認值是0
??? printf("height:%ld width:%ld/n",lua_tointeger(l,-1),lua_tointeger(l,-2)) ;
?int n = lua_gettop(l);???????????????? //要聲明變量等于lua_gettop()
??? lua_pop(l,1) ;??????????????????????? //恢復lua的棧
??? int a = 11 ;
??? int b = 12 ;
??? lua_getglobal(l,"sum");?????????????? //調用lua中的函數sum
??? lua_pushinteger(l,a) ;
??? lua_pushinteger(l,b) ;
??? int m = lua_gettop(l);
??? ret = lua_pcall(l,2,1,0) ;
??? //if ( ret != 0 ) err_return(-1,"lua_pcall failed:%s",lua_tostring(l,-1)) ;
??? printf("sum:%d + %d = %ld/n",a,b,lua_tointeger(l,-1)) ;
??? lua_pop(l,1) ;
??? const char str1[] = "hello" ;
??? const char str2[] = "world" ;
??? lua_getglobal(l,"mystrcat");????????? //調用lua中的函數mystrcat
??? lua_pushstring(l,str1) ;
??? lua_pushstring(l,str2) ;
??? ret = lua_pcall(l,2,1,0) ;
?? // if ( ret != 0 ) err_return(-1,"lua_pcall failed:%s",lua_tostring(l,-1)) ;
??? printf("mystrcat:%s%s = %s/n",str1,str2,lua_tostring(l,-1)) ;
??? lua_pop(l,1) ;
??? //lua_pushcfunction(l,csum) ;???????? //注冊在lua中使用的c函數
??? //lua_setglobal(l,"csum") ;?????????? //綁定到lua中的名字csum
?lua_register(l,"csum",csum);
??? lua_getglobal(l,"mysum");?????????? //調用lua中的mysum函數,該函數調用本程序中定義的csum函數實現加法
??? lua_pushinteger(l,a) ;
??? lua_pushinteger(l,b) ;
??? ret = lua_pcall(l,2,1,0) ;
??? //if ( ret != 0 ) err_return(-1,"lua_pcall failed:%s",lua_tostring(l,-1)) ;
??? printf("mysum:%d + %d = %ld/n",a,b,lua_tointeger(l,-1)) ;
??? lua_pop(l,1) ;
??? lua_close(l) ;???????????????????? //釋放lua運行環境
??? return 0 ;
}
前言
對于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程序展示一下,怎么能夠行呢?
復制代碼 代碼如下:#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;
}
如果你沒有接觸過C API,對于上面這段代碼,你肯定不會明白它是干什么的。什么也不說,你運行一下吧。然后輸入Lua語句,看看運行結果。
先對上述代碼引入的幾個頭文件進行解釋一下:
頭文件lua.h定義了Lua提供的基礎函數,包括創建Lua環境、調用Lua函數、讀寫Lua環境中全局變量,以及注冊供Lua調用的新函數等等;
頭文件lauxlib.h定義了輔助庫提供的輔助函數,它的所有定義都以LuaL_開頭。輔助庫是一個使用lua.h中API編寫出的一個較高的抽象層。Lua的所有標準庫編寫都用到了輔助庫;輔助庫主要用來解決實際的問題。輔助庫并沒有直接訪問Lua的內部,它都是用官方的基礎API來完成所有工作的;
頭文件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,并附上一條錯誤消息。在后面的博文中,會有這方面的代碼實例的。
總結
- 上一篇: 基于Lua脚本语言的嵌入式UART通信的
- 下一篇: Java程序内存分析:使用mat工具分析