Lua是一個嵌入式的腳本語言,它不僅可以單獨使用還能與其它語言混合調用。
Lua與其它腳本語言相比,其突出優勢在於:
- 可擴展性。Lua的擴展性非常卓越,以至於很多人把Lua用作搭建領域語言的工具(注:比如遊戲腳本)。Lua被設計爲易於擴展的,可以通過Lua代碼或者 C代碼擴展,Lua的很多功能都是通過外部庫來擴展的。Lua很容易與C/C++、java、fortran、Smalltalk、Ada,以及其他語言接口。
- 簡單。Lua本身簡單,小巧;內容少但功能強大,這使得Lua易於學習,很容易實現一些小的應用。他的完全發佈版(代碼、手冊以及某些平臺的二進制文件)僅用一張軟盤就可以裝得下。
- 高效率。Lua有很高的執行效率,統計表明Lua是目前平均效率最高的腳本語言。
- 與平臺無關。Lua幾乎可以運行在所有我們聽說過的系統上,如NextStep、OS/2、PlayStation II (Sony)、Mac OS-9、OS X、BeOS、MS-DOS、IBM mainframes、EPOC、PalmOS、MCF5206eLITE Evaluation Board、RISC OS,及所有的Windows和Unix。Lua不是通過使用條件編譯實現平臺無關,而是完全使用ANSI (ISO) C,這意味着只要你有ANSI C編譯器你就可以編譯並使用Lua。
當然,爲了方便維護,最好還是先把Lua編譯成庫文件再加入工程。方法如下:
GCC 直接在Lua所在目錄下make [環境] 這裏的[環境]可以是:aix ansi bsd freebsd generic linux macosx mingw posix solaris 如果你的環境不在這個列表中,你可以試試ansi或posix。 VC 在命令行環境下進入Lua所在目錄,執行etc/luavs.bat編譯。 C++Builder 請看我的Blog,如何在C++Builder裏編譯Lua
頭文件
因爲Lua是用C語言寫的,除非編譯lua庫時指定編譯器強制以C++方式編譯,否則在C++工程中應該這樣包含lua頭文件:- extern "C" {
- #include "lua.h"
- #include "lualib.h"
- #include "lauxlib.h"
- }
例一,簡單運行Lua代碼
- extern "C" {
- #include "lua.h"
- #include "lualib.h"
- #include "lauxlib.h"
- }
- #include <iostream>
- #include <string>
- using namespace std;
- int main()
- {
- lua_State *L = lua_open(); //初始化lua
- luaL_openlibs(L); //載入所有lua標準庫
- string s;
- while(getline(cin,s)) //從cin中讀入一行到s
- {
- //載入s裏的lua代碼後執行
- bool err = luaL_loadbuffer(L, s.c_str(), s.length(),
- "line") || lua_pcall(L, 0, 0, 0);
- if(err)
- {
- //如果錯誤,顯示
- cerr << lua_tostring(L, -1);
- //彈出錯誤信息所在的最上層棧
- lua_pop(L, 1);
- }
- }
- lua_close(L);//關閉
- return 0;
- }
這已經是一個功能完備的交互方式Lua解釋器了。
輸入print "hello world" 輸出hello world 輸入for i=1,10 do print(i) end 輸出從1到10
要調用Lua,首先要使用lua_open(對於5.0以後版本的Lua,建議使用luaL_newstate代替)產生一個lua_State,在使用完後調用lua_close關閉。
所有Lua與C之間交換的數據都是通過Lua中的棧來中轉的。
在本例中:
luaL_loadbuffer的功能是載入並編譯內存中的一段Lua代碼,然後作爲一個代碼塊(稱爲chunk)壓入棧中,其中的最後一個參數作爲代碼塊的名稱用於調試。和它功能類似的還有luaL_loadfile(載入文件),luaL_loadstring(載入字符串,本例中也可用它代替luaL_loadbuffer)。它們有一個相同的前綴:luaL_,爲了簡化編程,Lua C API將庫中的核心函數包裝後作爲輔助函數提供一些常用功能,它們的形式都是luaL_*,如這裏的三個luaL_load*都調用了lua_load。
lua_pcall從棧頂取得函數並執行,如果出錯,則返回一個非0值並把錯誤信息壓入棧頂。關於它的更詳細信息會在“例三,在C++中調用Lua子函數”中介紹。
如果宿主程序檢測到錯誤,就用lua_tostring從棧頂取得錯誤信息轉成字符串輸出,然後彈出這個錯誤信息。
lua_tostring裏的第二個參數指定要操作的數據處於棧的哪個位置,因爲所有的數據只能通過棧來交換,所以很多Lua的C API都會要求指定棧的位置。1表示在棧中的第一個元素(也就是第一個被壓入棧的),下一個索引是2,以此類推。我們也可以用棧頂作爲參照來存取元素,使用負數:-1指出棧頂元素(也就是最後被壓入的),-2指出它的前一個元素,以此類推。
例二,與Lua交換數據
- extern "C" {
- #include "lua.h"
- #include "lualib.h"
- #include "lauxlib.h"
- }
- #include <iostream>
- #include <string>
- using namespace std;
- int main()
- {
- //Lua示例代碼
- char *szLua_code =
- "r = string.gsub(c_Str, c_Mode, c_Tag) --宿主給的變量 "
- "u = string.upper(r)";
- //Lua的字符串模式
- char *szMode = "(%w+)%s*=%s*(%w+)";
- //要處理的字符串
- char *szStr = "key1 = value1 key2 = value2";
- //目標字符串模式
- char *szTag = "<%1>%2</%1>";
- lua_State *L = luaL_newstate();
- luaL_openlibs(L);
- //把一個數據送給Lua
- lua_pushstring(L, szMode);
- lua_setglobal(L, "c_Mode");
- lua_pushstring(L, szTag);
- lua_setglobal(L, "c_Tag");
- lua_pushstring(L, szStr);
- lua_setglobal(L, "c_Str");
- //執行
- bool err = luaL_loadbuffer(L, szLua_code, strlen(szLua_code),
- "demo") || lua_pcall(L, 0, 0, 0);
- if(err)
- {
- //如果錯誤,顯示
- cerr << lua_tostring(L, -1);
- //彈出棧頂的這個錯誤信息
- lua_pop(L, 1);
- }
- else
- {
- //Lua執行後取得全局變量的值
- lua_getglobal(L, "r");
- cout << "r = " << lua_tostring(L,-1) << endl;
- lua_pop(L, 1);
- lua_getglobal(L, "u");
- cout << "u = " << lua_tostring(L,-1) << endl;
- lua_pop(L, 1);
- }
- lua_close(L);
- return 0;
- }
這段代碼把字符串中的key=value字符串全部轉換成XML格式<key>value</key>
在這個例子中,C++程序通過調用lua_pushstring把C字符串壓入棧頂,lua_setglobal的作用是把棧頂的數據傳到Lua環境中作爲全局變量。
執行代碼完成後,使用lua_getglobal從Lua環境中取得全局變量壓入棧頂,然後使用lua_tostring把棧頂的數據轉成字符串。由於lua_tostring本身沒有出棧功能,所以爲了平衡(即調用前與調用後棧裏的數據量不變),使用lua_pop彈出由lua_setglobal壓入的數據。
從上面的例子可以看出,C++和Lua之間一直圍繞着棧在轉,可見棧是極爲重要的。有必要列出一些Lua C API中的主要棧操作先,它們的作用直接可以從函數名中看出。
壓入元素到棧裏
void lua_pushnil (lua_State *L); void lua_pushboolean (lua_State *L, int bool); void lua_pushnumber (lua_State *L, double n); void lua_pushlstring (lua_State *L, const char *s, size_t length); void lua_pushstring (lua_State *L, const char *s); void lua_pushcfunction (lua_State *L, lua_CFunction fn);
查詢棧裏的元素
lua_isnil (lua_State *L, int index); lua_isboolean (lua_State *L, int index); int lua_isnumber (lua_State *L, int index); int lua_isstring (lua_State *L, int index); int lua_isfunction (lua_State *L, int index); int lua_istable (lua_State *L, int index); int lua_isuserdata (lua_State *L, int index); lua_islightuserdata (lua_State *L, int index); lua_isthread (lua_State *L, int index);
轉換棧裏的元素
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); const char * lua_tolstring (lua_State *L, int idx, size_t *len); size_t lua_strlen (lua_State *L, int index); 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);
Lua棧的維護
int lua_gettop (lua_State *L); 取得棧頂元素的索引,即棧中元素的個數 void lua_settop (lua_State *L, int index); 設置棧頂索引,即設置棧中元素的個數,如果index<0,則從棧頂往下數,下同 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); 從棧頂彈出元素值並將其設置到指定索引位置,棧中的數目減一 int lua_checkstack (lua_State *L, int extra); 確保堆棧上至少有 extra 個空位。如果不能把堆棧擴展到相應的尺寸,函數返回 false 。這個函數永遠不會縮小堆棧。 int lua_pop(L,n) 從棧頂彈出n個元素,它是一個lua_settop的包裝:#define lua_pop(L,n) lua_settop(L, -(n)-1)
表的操作
上面的列表中並沒有lua_pushtable和lua_totable,那麼怎樣取得或設置Lua中的table數據呢?
在Lua中,table是一個很重要的數據類型,在table中不僅可以象C中的數據一樣放一組數據,還可以象map一樣以key=value的方式存放數據,如Lua代碼中的:
tb = {"abc",12,true,x=10,y=20,z=30}
前三個數據可以用tb[1]~tb[3]取得而後三個數據通過tb.x, tb.y, tb.z取得
儘管看起來很牛叉,不過剝開神奇的外衣,實際上Lua的table中,所有的數據都是以key=value的形式存放的,這句Lua代碼也可以寫成:
tb = {[1]="abc", [2]=12, [3] = true, ["x"]=10, ["y"]=20, ["z"]=30}
它的形式就是[key]=value,所謂的tb.x只是tb["x"]的語法糖而已,如果願意,也可以用tb["x"]取得這個數據10。我們把上面的例子改成使用表的
- ...
- int main()
- {
- //Lua示例代碼,使用table
- char *szLua_code =
- "x = {} --用於存放結果的table "
- "x[1],x[2] = string.gsub(c.Str, c.Mode, c.Tag) --x[1]裏是結果,x[2]裏是替換次數 "
- "x.u = string.upper(x[1])";
- //Lua的字符串模式
- char *szMode = "(%w+)%s*=%s*(%w+)";
- //要處理的字符串
- char *szStr = "key1 = value1 key2 = value2";
- //目標字符串模式
- char *szTag = "<%1>%2</%1>";
- lua_State *L = luaL_newstate();
- luaL_openlibs(L);
- //把一個tabele送給Lua
- lua_newtable(L); //新建一個table並壓入棧頂
- lua_pushstring(L, "Mode");// key
- lua_pushstring(L, szMode);// value
- //設置newtable[Mode]=szMode
- //由於上面兩次壓棧,現在table元素排在棧頂往下數第三的位置
- lua_settable(L, -3);
- //lua_settable會自己彈出上面壓入的key和value
- lua_pushstring(L, "Tag");// key
- lua_pushstring(L, szTag);// value
- lua_settable(L, -3); //設置newtable[Tag]=szTag
- lua_pushstring(L, "Str");// key
- lua_pushstring(L, szStr);// value
- lua_settable(L, -3); //設置newtable[Str]=szStr
- lua_setglobal(L,"c"); //將棧頂元素(newtable)置爲Lua中的全局變量c
- //執行
- bool err = luaL_loadbuffer(L, szLua_code, strlen(szLua_code),
- "demo") || lua_pcall(L, 0, 0, 0);
- if(err)
- {
- //如果錯誤,顯示
- cerr << lua_tostring(L, -1);
- //彈出棧頂的這個錯誤信息
- lua_pop(L, 1);
- }
- else
- {
- //Lua執行後取得全局變量的值
- lua_getglobal(L, "x");
- //這個x應該是個table
- if(lua_istable(L,-1))
- {
- //取得x.u,即x["u"]
- lua_pushstring(L,"u"); //key
- //由於這次壓棧,x處於棧頂第二位置
- lua_gettable(L,-2);
- //lua_gettable會彈出上面壓入的key,然後把對應的value壓入
- //取得數據,然後從棧中彈出這個value
- cout << "x.u = " << lua_tostring(L,-1) << endl;
- lua_pop(L, 1);
- //取得x[1]和x[2]
- for(int i=1; i<=2; i++)
- {
- //除了key是數字外,與上面的沒什麼區別
- lua_pushnumber(L,i);
- lua_gettable(L,-2);
- cout << "x[" << i <<"] = " << lua_tostring(L,-1) << endl;
- lua_pop(L, 1);
- }
- }
- //彈出棧頂的x
- lua_pop(L, 1);
- }
- lua_close(L);
- return 0;
- }
void lua_newtable (lua_State *L); 新建一個空的table並壓入棧頂。 void lua_settable (lua_State *L, int idx); lua_settable以table在棧中的索引作爲參數,並將棧頂的key和value出棧,用這兩個值修改table。 void lua_gettable (lua_State *L, int idx); lua_gettable以table在棧中的索引作爲參數,彈出棧頂的元素作爲key,返回與key對應的value並壓入棧頂。 最後,Lua告別針對table提供了存取函數 void lua_rawgeti (lua_State *L, int idx, int n) 取得table[n]並放到棧頂,上例中69-70行的lua_pushnumber(L,i);lua_gettable(L,-2);可以用lua_rawgeti(L,-1)代替。 lua_getfield (lua_State *L, int idx, const char *k) 取得table.k並放到棧頂,上例中57-59行的lua_pushstring(L,"u");lua_gettable(L,-2);可以替換成lua_getfield(L,-1,"u")。 void lua_setfield (lua_State *L, int idx, const char *k) 把棧頂的數據作爲value放入table.k中,上例中的形如lua_pushstring(L, "key");lua_pushstring(L, value);lua_settable(L, -3);可以改成lua_pushstring(L, value);lua_setfield(L,-2,"key");的形式。 void lua_rawseti (lua_State *L, int idx, int n) 把棧頂的數據作爲value放入table[n]中
例三,在C++中調用Lua子函數
在Lua中,函數和boolean一樣也屬於基本數據類型,所以同樣可以使用lua_getglobal來取得函數,剩下的問題只是怎樣執行它(函數元素)的問題了。- ...
- int main()
- {
- //Lua示例代碼,是一個函數
- char *szLua_code =
- "function gsub(Str, Mode, Tag)"
- " a,b = string.gsub(Str, Mode, Tag) "
- " c = string.upper(a) "
- " return a,b,c --多個返回值 "
- "end";
- //Lua的字符串模式
- char *szMode = "(%w+)%s*=%s*(%w+)";
- //要處理的字符串
- char *szStr = "key1 = value1 key2 = value2";
- //目標字符串模式
- char *szTag = "<%1>%2</%1>";
- lua_State *L = luaL_newstate();
- luaL_openlibs(L);
- //執行
- bool err = luaL_loadbuffer(L, szLua_code, strlen(szLua_code),
- "demo") || lua_pcall(L, 0, 0, 0);
- if(err)
- {
- cerr << lua_tostring(L, -1);
- lua_pop(L, 1);
- }
- else
- {
- //Lua執行後取得全局變量的值
- lua_getglobal(L, "gsub");
- if(lua_isfunction(L,-1)) //確認一下是個函數
- {
- //依次放入三個參數
- lua_pushstring(L,szStr);
- lua_pushstring(L,szMode);
- lua_pushstring(L,szTag);
- //調用,我們有3個參數,要得到2個結果
- //你可能注意到gsub函數返回了3個,不過我們只要2個,這沒有問題
- //沒有使用錯誤處理回調,所以lua_pcall最後一個參數是0
- if(0 != lua_pcall(L, 3, 2, 0))
- {
- //如果錯誤,顯示
- cerr << lua_tostring(L, -1);
- lua_pop(L, 1);
- }
- else
- {
- //正確,得到兩個結果,注意在棧裏的順序
- cout << "a = " << lua_tostring(L, -2) << endl;
- cout << "b = " << lua_tostring(L, -1) << endl;
- //彈出這兩個結果
- lua_pop(L, 2);
- }
- }
- else
- {
- lua_pop(L,1);
- }
- }
- lua_close(L);
- return 0;
- }
調用Lua子函數使用的是lua_pcall函數,我們的所有例子中都有這個函數,它的說明如下:
lua_pcall (lua_State *L, int nargs, int nresults, int errfunc);
作用:以保護模式調用一個函數。要調用一個函數請遵循以下協議:首先,要調用的函數應該被壓入堆棧;接着,把需要傳遞給這個函數的參數按正序壓棧;這是指第一個參數首先壓棧。最後調用lua_pcall;
nargs 是你壓入堆棧的參數個數。當函數調用完畢後,所有的參數以及函數本身都會出棧。而函數的返回值這時則被壓入堆棧。返回值的個數將被調整爲 nresults 個,除非 nresults 被設置成 LUA_MULTRET。在這種情況下,所有的返回值都被壓入堆棧中。 Lua 會保證返回值都放入棧空間中。函數返回值將按正序壓棧(第一個返回值首先壓棧),因此在調用結束後,最後一個返回值將被放在棧頂。
如果有錯誤發生的話, lua_pcall 會捕獲它,然後把單一的值(錯誤信息)壓入堆棧,然後返回錯誤碼。lua_pcall 總是把函數本身和它的參數從棧上移除。
如果 errfunc 是 0 ,返回在棧頂的錯誤信息就和原始錯誤信息完全一致。否則,這個函數會被調用而參數就是錯誤信息。錯誤處理函數的返回值將被 lua_pcall 作爲出錯信息返回在堆棧上。
例四,在Lua代碼中調用C++函數
能Lua代碼中調用C函數對Lua來說至關重要,讓Lua能真正站到C這個巨人的肩膀上。要寫一個能讓Lua調用的C函數,就要符合lua_CFunction定義:typedef int (*lua_CFunction) (lua_State *L);
當Lua調用C函數的時候,同樣使用棧來交互。C函數從棧中獲取她的參數,調用結束後將結果放到棧中,並返回放到棧中的結果個數。
這兒有一個重要的概念:用來交互的棧不是全局棧,每一個函數都有他自己的私有棧。當Lua調用C函數的時候,第一個參數總是在這個私有棧的index=1的位置。
- ...
- #include <complex> //複數
- //C函數,做複數計算,輸入實部,虛部。輸出絕對值和角度
- int calcComplex(lua_State *L)
- {
- //從棧中讀入實部,虛部
- double r = luaL_checknumber(L,1);
- double i = luaL_checknumber(L,2);
- complex<double> c(r,i);
- //存入絕對值
- lua_pushnumber(L,abs(c));
- //存入角度
- lua_pushnumber(L,arg(c)*180.0/3.14159);
- return 2;//兩個結果
- }
- int main()
- {
- char *szLua_code =
- "v,a = CalcComplex(3,4) "
- "print(v,a)";
- lua_State *L = luaL_newstate();
- luaL_openlibs(L);
- //放入C函數
- lua_pushcfunction(L, calcComplex);
- lua_setglobal(L, "CalcComplex");
- //執行
- bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
- if(err)
- {
- cerr << lua_tostring(L, -1);
- lua_pop(L, 1);
- }
- lua_close(L);
- return 0;
- }
#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n)))
這兩句代碼也就可寫成lua_register(L,"CalcComplex",calcComplex);
閉包(closure)
在編寫用於Lua的C函數時,我們可能需要一些類似於面向對象的能力,比如我們想在Lua中使用象這樣的一個計數器類:- struct CCounter{
- CCounter()
- :m_(0){}
- int count(){
- return ++i;
- }
- private:
- int m_;
- };
這時我們就需要一種機制讓數據與某個函數關聯,形成一個整體,這就是Lua中的閉包,而閉包裏與函數關聯的數據稱爲UpValue。
使用Lua閉包的方法是定義一個工廠函數,由它來指定UpValue的初值和對應的函數,如:
- ...
- //計算函數
- int count(lua_State *L)
- {
- //得到UpValue
- double m_ = lua_tonumber(L, lua_upvalueindex(1));
- //更改UpValue
- lua_pushnumber(L, ++m_);
- lua_replace(L, lua_upvalueindex(1));
- //返回結果(直接複製一份UpValue作爲結果)
- lua_pushvalue(L, lua_upvalueindex(1));
- return 1;
- }
- //工廠函數,把一個數字和count函數關聯打包後返回閉包。
- int newCount(lua_State *L)
- {
- //計數器初值(即UpValue)
- lua_pushnumber(L,0);
- //放入計算函數,告訴它與這個函數相關聯的數據個數
- lua_pushcclosure(L, count, 1);
- return 1;//一個結果,即函數體
- }
- int main()
- {
- char *szLua_code =
- "c1 = NewCount() "
- "c2 = NewCount() "
- "for i=1,5 do print(c1()) end "
- "for i=1,5 do print(c2()) end";
- lua_State *L = luaL_newstate();
- luaL_openlibs(L);
- //放入C函數
- lua_register(L,"NewCount",newCount);
- //執行
- bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
- if(err)
- {
- cerr << lua_tostring(L, -1);
- lua_pop(L, 1);
- }
- lua_close(L);
- return 0;
- }
執行結果是:
1 2 3 4 5 1 2 3 4 5
可以發現這兩個計算器之間沒有干擾,我們成功地在Lua中生成了兩個“計數器類”。
這裏的關鍵函數是lua_pushcclosure,她的第二個參數是一個基本函數(例子中是count),第三個參數是UpValue的個數(例子中爲 1)。在創建新的閉包之前,我們必須將關聯數據的初始值入棧,在上面的例子中,我們將數字0作爲初始值入棧。如預期的一樣, lua_pushcclosure將新的閉包放到棧內,因此閉包作爲newCounter的結果被返回。
實際上,我們之前使用的lua_pushcfunction只是lua_pushcclosure的一個特例:沒有UpValue的閉包。查看它的聲明可 以知道它只是一個宏而已:
#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0)
在count函數中,通過lua_upvalueindex(i)得到當前閉包的UpValue所在的索引位置,查看它的定義可以發現它只是一個簡單的 宏:
#define lua_upvalueindex(i) (LUA_GLOBALSINDEX-(i))
宏裏的LUA_GLOBALSINDEX是一個僞索引,關於僞索引的知識請看下節
僞索引
僞索引除了它對應的值不在棧中之外,其他都類似於棧中的索引。Lua C API中大部分接受索引作爲參數的函數,也都可以接受假索引作爲參數。僞索引被用來訪問線程的環境,函數的環境,Lua註冊表,還有C函數的UpValue。
- 線程的環境(也就是放全局變量的地方)通常在僞索引 LUA_GLOBALSINDEX 處。
- 正在運行的 C 函數的環境則放在僞索引 LUA_ENVIRONINDEX 之處。
- LUA_REGISTRYINDEX則存放着Lua註冊表。
- C函數UpValue的存放位置見上節。
LUA_GLOBALSINDEX位置上的table存放着所有的全局變量,比如這句
lua_getfield(L, LUA_GLOBALSINDEX, varname);
就是取得名爲varname的全局變量,我們之前一直使用的lua_getglobal就是這樣定義的:#define lua_getglobal(L,s) lua_getfield(L, LUA_GLOBALSINDEX, (s))
下面的代碼利用LUA_GLOBALSINDEX得到所有的全局變量
- int main()
- {
- char *szLua_code =
- "a=10 "
- "b=/"hello/" "
- "c=true";
- lua_State *L = luaL_newstate();
- luaL_openlibs(L);
- //執行
- bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
- if(err)
- {
- cerr << lua_tostring(L, -1);
- lua_pop(L, 1);
- }
- else
- {
- //遍歷LUA_GLOBALSINDEX所在的table得到
- lua_pushnil(L);
- while(0 != lua_next(L,LUA_GLOBALSINDEX))
- {
- // 'key' (在索引 -2 處) 和 'value' (在索引 -1 處)
- /*
- 在遍歷一張表的時候,不要直接對 key 調用 lua_tolstring ,
- 除非你知道這個 key 一定是一個字符串。
- 調用 lua_tolstring 有可能改變給定索引位置的值;
- 這會對下一次調用 lua_next 造成影響。
- 所以複製一個key到棧頂先
- */
- lua_pushvalue(L, -2);
- printf("%s - %s ",
- lua_tostring(L, -1), //key,剛纔複製的
- lua_typename(L, lua_type(L,-2))); //value,現在排在-2的位置了
- // 移除 'value' 和複製的key;保留源 'key' 做下一次疊代
- lua_pop(L, 2);
- }
- }
- lua_close(L);
- return 0;
- }
LUA_REGISTRYINDEX僞索引處也存放着一個table,它就是Lua註冊表(registry)。這個註冊表可以用來保存任何C代碼想保存 的Lua值。
加入到註冊表裏的數據相當於全局變量,不過只有C代碼可以存取而Lua代碼不能。因此用它來存儲函數庫(在下一節介紹)中的一些公共變量再好不過了。
函數庫
一個Lua庫實際上是一個定義了一系列Lua函數的代碼塊,並將這些函數保存在適當的地方,通常作爲table的域來保存。Lua的C庫就是這樣實現的。作爲一個完整的庫,我們還需要寫一個函數來負責把庫中的所有公共函數放到table裏,然後註冊到Lua全局變量裏,就像luaopen_*做的一樣。 Lua爲這種需求提供了輔助函數luaL_register,它接受一個C函數的列表和他們對應的函數名,並且作爲一個庫在一個table中註冊所有這些 函數。
下例中註冊了一個名爲Files的庫,定義了三個庫函數:FindFirst,FindNext,FindClose。
- extern "C" {
- #include "lua.h"
- #include "lualib.h"
- #include "lauxlib.h"
- }
- #include <iostream>
- #include <string>
- #include <windows.h>
- using namespace std;
- //函數庫示例,Windows下查找文件功能
- //輸入:string路徑名
- //輸出:userdata存放Handle(如果沒找到,則是nil), string文件名
- int findfirst( lua_State *L )
- {
- WIN32_FIND_DATAA FindFileData;
- HANDLE hFind = ::FindFirstFileA(luaL_checkstring(L,1), &FindFileData);
- if(INVALID_HANDLE_VALUE == hFind)
- lua_pushnil(L);
- else
- lua_pushlightuserdata(L, hFind);
- lua_pushstring(L, FindFileData.cFileName);
- return 2;
- }
- //輸入:userdata:findfirst返回的Handle
- //輸出:string:文件名,如果沒找到,則返回nil
- int findnext( lua_State *L )
- {
- WIN32_FIND_DATAA FindFileData;
- if(::FindNextFileA(lua_touserdata(L,1),&FindFileData))
- lua_pushstring(L, FindFileData.cFileName);
- else
- lua_pushnil(L);
- return 1;
- }
- //輸入:userdata:findfirst返回的Handle
- //沒有輸出
- int findclose( lua_State *L )
- {
- ::FindClose(lua_touserdata(L,1));
- return 0;
- }
- //註冊函數庫
- static const struct luaL_reg lrFiles [] = {
- {"FindFirst", findfirst},
- {"FindNext", findnext},
- {"FindClose", findclose},
- {NULL, NULL} /* sentinel */
- };
- int luaopen_Files (lua_State *L) {
- luaL_register(L, "Files", lrFiles);
- return 1;
- }
- int main()
- {
- char* szLua_code=
- "hFind,sFile = Files.FindFirst('c:////*.*'); "
- "if hFind then "
- " repeat "
- " print(sFile) "
- " sFile = Files.FindNext(hFind) "
- " until sFile==nil; "
- " Files.FindClose(hFind) "
- "end";
- lua_State *L = luaL_newstate();
- luaL_openlibs(L);
- luaopen_Files(L);
- bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
- if(err)
- {
- cerr << lua_tostring(L, -1);
- lua_pop(L, 1);
- }
- lua_close(L);
- return 0;
- }
本例運行結果是顯示出C盤根目錄下所有的文件名。
Lua官方建議把函數庫寫進動態鏈接庫中(windows下.dll文件,linux下.so文件),這樣就可以在Lua代碼中使用loadlib函數動 態載入函數庫
例如,我們把上面的例子改成動態鏈接庫版本:
DLL代碼,假定生成的文件名爲fileslib.dll:
- extern "C" {
- #include "lua.h"
- #include "lualib.h"
- #include "lauxlib.h"
- }
- #include <windows.h>
- BOOL APIENTRY DllMain( HMODULE hModule,
- DWORD ul_reason_for_call,
- LPVOID lpReserved
- )
- {
- return TRUE;
- }
- //函數庫示例,Windows下查找文件功能
- //輸入:string路徑名
- //輸出:userdata存放Handle(如果沒找到,則是nil), string文件名
- int findfirst( lua_State *L )
- {
- WIN32_FIND_DATAA FindFileData;
- HANDLE hFind = ::FindFirstFileA(luaL_checkstring(L,1), &FindFileData);
- if(INVALID_HANDLE_VALUE == hFind)
- lua_pushnil(L);
- else
- lua_pushlightuserdata(L, hFind);
- lua_pushstring(L, FindFileData.cFileName);
- return 2;
- }
- //輸入:userdata:findfirst返回的Handle
- //輸出:string:文件名,如果沒找到,則返回nil
- int findnext( lua_State *L )
- {
- WIN32_FIND_DATAA FindFileData;
- if(::FindNextFileA(lua_touserdata(L,1),&FindFileData))
- lua_pushstring(L, FindFileData.cFileName);
- else
- lua_pushnil(L);
- return 1;
- }
- //輸入:userdata:findfirst返回的Handle
- //沒有輸出
- int findclose( lua_State *L )
- {
- ::FindClose(lua_touserdata(L,1));
- return 0;
- }
- //註冊函數庫
- static const struct luaL_reg lrFiles [] = {
- {"FindFirst", findfirst},
- {"FindNext", findnext},
- {"FindClose", findclose},
- {NULL, NULL} /* sentinel */
- };
- //導出,注意原型爲typedef int (*lua_CFunction) (lua_State *L);
- extern "C" __declspec(dllexport) int luaopen_Files (lua_State *L) {
- luaL_register(L, "Files", lrFiles);
- return 1;
- }
- extern "C" {
- #include "lua.h"
- #include "lualib.h"
- #include "lauxlib.h"
- }
- #include <iostream>
- #include <string>
- #include <windows.h>
- using namespace std;
- int main()
- {
- char* szLua_code=
- "fileslib = package.loadlib('fileslib.dll', 'luaopen_Files') "
- "fileslib() "
- "hFind,sFile = Files.FindFirst('c:////*.*'); "
- "if hFind then "
- " repeat "
- " print(sFile) "
- " sFile = Files.FindNext(hFind) "
- " until sFile==nil; "
- " Files.FindClose(hFind) "
- "end";
- lua_State *L = luaL_newstate();
- luaL_openlibs(L);
- bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
- if(err)
- {
- cerr << lua_tostring(L, -1);
- lua_pop(L, 1);
- }
- lua_close(L);
- return 0;
- }
Lua代碼裏使用package.loadlib得到動態鏈接庫中的luaopen_Files函數,然後調用它註冊到Lua中,如果動態鏈接庫中的導出 函數名稱滿足luaopen_<庫名>的話,還可以使用require直接載入。
比如,如果把本例中的DLL代碼裏的導出函數名luaopen_Files改成luaopen_fileslib的話,Lua代碼便可以改成:
- char* szLua_code=
- "require('fileslib') "
- "hFind,sFile = Files.FindFirst('c:////*.*'); "
- ...
例五,與Lua交換自定義數據
由於Lua中的數據類型遠不能滿足C語言的需要,爲此Lua提供了userdata,一個userdata提供了一個在Lua中沒有預定義操作的raw內 存區域。在例四的函數庫代碼中我們已經使用過lightuserdata,它是userdata的一個特例:一個表示C指針的值(也就是一個void *類型的值)。
下面的例子我們使用userdata來給Lua提供一個窗體類用於建立,顯示窗體。爲了簡化窗體控制代碼,在C函數中我們使用了C++Builder的 VCL庫,所以下面的代碼要在C++Builder下編譯才能通過。當然,稍微修改一下也可以使用MFC,QT,wxWidget等來代替。
- //---------------------------------------------------------------------------
- #include <vcl.h>
- extern "C" {
- #include "lua.h"
- #include "lualib.h"
- #include "lauxlib.h"
- }
- #include <iostream>
- #pragma hdrstop
- //---------------------------------------------------------------------------
- #pragma argsused
- typedef TWinControl* PWinControl;
- //創建窗體,輸入父窗體(或nil),類型,標題
- //輸出創建後的窗體
- int newCtrl(lua_State *L)
- {
- //input:TWinControl *Parent, type(TForm,TButton,TEdit), text(optional)
- TWinControl *Parent = NULL;
- //從userdata中取得TWinControl*
- if(lua_isuserdata(L,1))
- Parent = *(PWinControl*)lua_touserdata(L,1);
- String Type = UpperCase(luaL_checkstring(L, 2));
- String Text = lua_tostring(L, 3);
- TWinControl *R = NULL;
- if(Type == "FORM")
- {
- R = new TForm(Application);
- }
- else if(Type == "BUTTON")
- {
- R = new TButton(Application);
- }
- else if(Type == "EDIT")
- {
- R = new TEdit(Application);
- }
- else
- {
- luaL_error(L, "unknow type!");
- }
- if(Parent)
- R->Parent = Parent;
- if(!Text.IsEmpty())
- ::SetWindowText(R->Handle, Text.c_str());
- //新建userdata,大小爲sizeof(PWinControl),用於存放上面生成的窗體指針
- PWinControl* pCtrl = (PWinControl*)lua_newuserdata(L,sizeof(PWinControl));
- *pCtrl = R;
- return 1;
- }
- //顯示窗體
- int showCtrl(lua_State *L)
- {
- //input: TWinControl*, for TForm, use ShowModal
- TWinControl* Ctrl = *(PWinControl*)lua_touserdata(L,1);
- TForm *fm = dynamic_cast<TForm*>(Ctrl);
- if(fm)
- fm->ShowModal();
- else
- Ctrl->Show();
- return 0;
- }
- //定位窗體,輸入窗體,左,上,右,下
- int posCtrl(lua_State *L)
- {
- //input: TWinControl*, Left, Top, Right, Bottom
- TWinControl* Ctrl = *(PWinControl*)lua_touserdata(L,1);
- Ctrl->BoundsRect = TRect(
- luaL_checkint(L, 2),
- luaL_checkint(L, 3),
- luaL_checkint(L, 4),
- luaL_checkint(L, 5));
- return 0;
- }
- //刪除窗體
- int delCtrl(lua_State *L)
- {
- //input: TWinControl*
- TWinControl* Ctrl = *(PWinControl*)lua_touserdata(L,1);
- delete Ctrl;
- return 0;
- }
- //把這些函數作爲VCL函數庫提供給Lua
- static const struct luaL_reg lib_VCL [] = {
- {"new", newCtrl},
- {"del", delCtrl},
- {"pos", posCtrl},
- {"show", showCtrl},
- {NULL, NULL}
- };
- int luaopen_VCL (lua_State *L) {
- luaL_register(L, "VCL", lib_VCL);
- return 1;
- }
- int main(int argc, char* argv[])
- {
- char* szLua_code=
- "fm = VCL.new(nil,'Form','Lua Demo'); " //新建主窗體fm
- "VCL.pos(fm, 200, 200, 500, 300); " //定位
- "edt = VCL.new(fm, 'Edit', 'Hello World'); " //在fm上建立一個編輯框edt
- "VCL.pos(edt, 5, 5, 280, 28); "
- "btn = VCL.new(fm, 'Button', 'Haha'); " //在fm上建立一個按鈕btn
- "VCL.pos(btn, 100, 40, 150, 63); "
- "VCL.show(edt); "
- "VCL.show(btn); "
- "VCL.show(fm); " //顯示
- "VCL.del(fm);"; //刪除
- lua_State *L = luaL_newstate();
- luaL_openlibs(L);
- luaopen_VCL(L);
- bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
- if(err)
- {
- std::cerr << lua_tostring(L, -1);
- lua_pop(L, 1);
- }
- lua_close(L);
- return 0;
- }
- //---------------------------------------------------------------------------
使用metatable提供面向對象調用方式
上面的VCL代碼庫爲Lua提供了GUI的支持,但是看那些Lua代碼,還處於面向過程時期。如何能把VCL.show(edt)之類的代碼改成edt: show()這樣的形式呢?還是先看代碼:- //---------------------------------------------------------------------------
- #include <vcl.h>
- extern "C" {
- #include "lua.h"
- #include "lualib.h"
- #include "lauxlib.h"
- }
- #include <iostream>
- #pragma hdrstop
- //---------------------------------------------------------------------------
- #pragma argsused
- typedef TWinControl* PWinControl;
- //創建窗體,輸入父窗體(或nil),類型,標題
- //輸出創建後的窗體
- int newCtrl(lua_State *L)
- {
- //input:TWinControl *Parent, type(TForm,TButton,TEdit), text(optional)
- TWinControl *Parent = NULL;
- if(lua_isuserdata(L,1))
- Parent = *(PWinControl*)luaL_checkudata(L,1,"My_VCL");
- String Type = UpperCase(luaL_checkstring(L, 2));
- String Text = lua_tostring(L, 3);
- TWinControl *R = NULL;
- if(Type == "FORM")
- R = new TForm(Application);
- else if(Type == "BUTTON")
- R = new TButton(Application);
- else if(Type == "EDIT")
- R = new TEdit(Application);
- else
- luaL_error(L, "unknow type!");
- if(Parent)
- R->Parent = Parent;
- if(!Text.IsEmpty())
- ::SetWindowText(R->Handle, Text.c_str());
- //output TWinControl*
- PWinControl* pCtrl = (PWinControl*)lua_newuserdata(L,sizeof(PWinControl));
- *pCtrl = R;
- //關聯metatable
- luaL_getmetatable(L, "My_VCL");
- lua_setmetatable(L, -2);
- return 1;
- }
- //顯示窗體
- int showCtrl(lua_State *L)
- {
- //input: TWinControl*, for TForm, use ShowModal
- TWinControl* Ctrl = *(PWinControl*)luaL_checkudata(L,1,"My_VCL");
- TForm *fm = dynamic_cast<TForm*>(Ctrl);
- if(fm)
- fm->ShowModal();
- else
- Ctrl->Show();
- return 0;
- }
- //定位窗體,輸入窗體,左,上,右,下
- int posCtrl(lua_State *L)
- {
- //input: TWinControl*, Left, Top, Right, Bottom
- TWinControl* Ctrl = *(PWinControl*)luaL_checkudata(L,1,"My_VCL");
- Ctrl->BoundsRect = TRect(
- luaL_checkint(L, 2),
- luaL_checkint(L, 3),
- luaL_checkint(L, 4),
- luaL_checkint(L, 5));
- return 0;
- }
- //刪除窗體
- int delCtrl(lua_State *L)
- {
- //input: TWinControl*
- TWinControl* Ctrl = *(PWinControl*)luaL_checkudata(L,1,"My_VCL");
- delete Ctrl;
- return 0;
- }
- //把這些函數作爲VCL函數庫提供給Lua
- static const struct luaL_reg lib_VCL [] = {
- {"new", newCtrl},
- {"del", delCtrl},
- {"pos", posCtrl},
- {"show", showCtrl},
- {NULL, NULL}
- };
- int luaopen_VCL (lua_State *L) {
- //建立metatable
- luaL_newmetatable(L, "My_VCL");
- //查找索引,把它指向metatable自身(因爲稍後我們會在metatable里加入一些成員)
- lua_pushvalue(L, -1);
- lua_setfield(L,-2,"__index");
- //pos方法
- lua_pushcfunction(L, posCtrl);
- lua_setfield(L,-2,"pos");
- //show方法
- lua_pushcfunction(L, showCtrl);
- lua_setfield(L,-2,"show");
- //析構,如果表裏有__gc,Lua的垃圾回收機制會調用它。
- lua_pushcfunction(L, delCtrl);
- lua_setfield(L,-2,"__gc");
- luaL_register(L, "VCL", lib_VCL);
- return 1;
- }
- int main(int argc, char* argv[])
- {
- char* szLua_code=
- "local fm = VCL.new(nil,'Form','Lua Demo'); " //新建主窗體fm
- "fm:pos(200, 200, 500, 300); " //定位
- "local edt = VCL.new(fm, 'Edit', 'Hello World'); " //在fm上建立一個編輯框edt
- "edt:pos(5, 5, 280, 28); "
- "local btn = VCL.new(fm, 'Button', 'Haha'); " //在fm上建立一個按鈕btn
- "btn:pos(100, 40, 150, 63); "
- "edt:show(); "
- "btn:show(); "
- "fm:show(); "; //顯示
- //"VCL.del(fm);"; //不再需要刪除了,Lua的垃圾回收在回收userdata地會調用metatable.__gc。
- lua_State *L = luaL_newstate();
- luaL_openlibs(L);
- luaopen_VCL(L);
- bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
- if(err)
- {
- std::cerr << lua_tostring(L, -1);
- lua_pop(L, 1);
- }
- lua_close(L);
- return 0;
- }
- //---------------------------------------------------------------------------
我們這兒用到的輔助函數有:
int luaL_newmetatable (lua_State *L, const char *tname); 創建一個新表(用於metatable),將新表放到棧頂並在註冊表中建立一個類型名與之聯繫。 void luaL_getmetatable (lua_State *L, const char *tname); 獲取註冊表中tname對應的metatable。 int lua_setmetatable (lua_State *L, int objindex); 把一個table彈出堆棧,並將其設爲給定索引處的值的 metatable。 void *luaL_checkudata (lua_State *L, int index, const char *tname); 檢查在棧中指定位置的對象是否爲帶有給定名字的metatable的userdata。
我們只改動了luaopen_VCL和newCtrl函數。
在luaopen_VCL裏,我們建立了一個metatable,然後讓它的__index成員指向自身,並加入了pos,show函數成員和__gc函 數成員。
在newCtrl裏,我們把luaopen_VCL裏建立的metatable和新建的userdata關聯,於是:
- 對userdata的索引操作就會轉向metatable.__index
- 因爲metatable.__index是metatable自身,所以就在這個metatable裏查找
- 這樣,對userdata的pos、show索引轉到metatable裏的pos和show上,它們指向的是我們的C函數posCtrl和 posShow。
- 最後,當Lua回收這些userdata前,會調用metatable.__gc(如果有的話),我們已經把metatable.__gc指向了C函數 delCtrl。
關於metatable的知識已超出本文討論範圍,請參考Lua官方手冊。
例六,使用C++包裝類
儘管用Lua的C API已經可以方便地寫出與Lua交互的程序了,不過對於用慣C++的人來說還是更願意用C++的方式來解決問題。於是開源社區就出現了不少Lua C API的C++的wrap,比如:LuaBind,LuaPlus,toLua這裏介紹的是LuaBind庫,下載
它在Windows下貌似只能支持MSVC和ICC,好在Lua可以支持動態庫的載入,所以用VC+LuaBind寫Lua庫,再用C++Builder調用也是個好主意。
在VC使用LuaBind的方法是把LuaBind的src目錄下的cpp文件加入工程(當然也可以先編譯成靜態庫),加入Lua庫,設置LuaBind,Lua和Boost的頭文件路徑。
頭文件:
- //Lua頭文件
- extern "C"
- {
- #include <lua.h>
- #include <lualib.h>
- #include <lauxlib.h>
- }
- //LuaBind頭文件
- #include <luabind/luabind.hpp>
在C++中調用Lua函數
調用Lua函數那是最簡單不過的事情了,用LuaBind的call_function()模板函數就可以了:- int main(
- // 建立新的Lua環境
- lua_State *myLuaState = luaL_newstate();
- // 讓LuaBind“認識”這個Lua環境
- luabind::open(myLuaState);
- // 定義一個叫add的Lua函數
- luaL_dostring(
- myLuaState,
- "function add(first, second) "
- " return first + second "
- "end "
- );
- //調用add函數
- cout << "Result: "
- << luabind::call_function<int>(myLuaState, "add", 2, 3)
- << endl;
- lua_close(myLuaState);
- }
在本例中我們先使用Lua C API產生一個Lua線程環境,然後調用luabind::open()讓LuaBind關聯這個線程環境,在使用LuaBind之前這步是必須做的,它要在Lua環境中註冊一些LuaBind專用的數據。
在執行完Lua代碼之後,我們使用luabind::call_function<int>調用了Lua裏的add函數,返回值是int。
在Lua代碼中調用C++函數
從前面的文章裏我們知道在Lua調用C函數需要經常操作棧,而LuaBind幫我們做了這些工作,下面的例子把print_hello函數送給Lua腳本調用:- void print_hello(int number) {
- cout << "hello world " << number << endl;
- }
- int main(
- // 建立新的Lua環境
- lua_State *myLuaState = lua_open();
- // 讓LuaBind“認識”這個Lua環境
- luabind::open(myLuaState);
- // 添加print_hello函數到Lua環境中
- luabind::module(myLuaState) [
- luabind::def("print_hello", print_hello)
- ];
- // 現在Lua中可以調用print_hello了
- luaL_dostring(
- myLuaState,
- "print_hello(123) "
- );
- lua_close(myLuaState);
- }
向Lua環境加入函數或其它東東的方法是:
luabind::module(lua_State* L, char const* name = 0) [ ... ];其中module函數中的第二個指定要加入的東東所處的名空間(其實就是table),如果爲0,則處於全局域之下。
在中括號裏的luabind::def把print_hello函數提供給Lua環境,第一個參數是Lua中使用的函數名。
如果要定義多個函數,可以使用逗號分隔。
在Lua代碼中使用C++類
如果我們直接使用Lua C API向Lua腳本註冊一個C++類,一般是使用userdata+metatable的方法,就象我們在例五中做的一樣。這樣做盡管難度不大,卻非常繁瑣而且不方便維護。使用LuaBind我們就可以更方便地向Lua腳本註冊C++類了,例:
- class NumberPrinter {
- public:
- NumberPrinter(int number) :
- m_number(number) {}
- void print() {
- cout << m_number << endl;
- }
- private:
- int m_number;
- };
- int main() {
- lua_State *myLuaState = lua_open();
- luabind::open(myLuaState);
- // 使用LuaBind導出NumberPrinter類
- luabind::module(myLuaState) [
- luabind::class_<NumberPrinter>("NumberPrinter")
- .def(luabind::constructor<int>())
- .def("print", &NumberPrinter::print)
- ];
- // 現在Lua中可以使用NumberPinter類了
- luaL_dostring(
- myLuaState,
- "Print2000 = NumberPrinter(2000) "
- "Print2000:print() "
- );
- lua_close(myLuaState);
- }
爲了註冊一個類,LuaBind提供了class_類。它有一個重載過的成員函數 def() 。這個函數被用來註冊類的成員函數、操作符、構造器、枚舉和屬性。
它將返回this指針,這樣我們就可以方便地直接註冊更多的成員。
屬性
LuaBind 也可以導出類成員變量:- template<typename T>
- struct Point {
- Point(T X, T Y) :
- X(X), Y(Y) {}
- T X, Y;
- };
- template<typename T>
- struct Box {
- Box(Point<T> UpperLeft, Point<T> LowerRight) :
- UpperLeft(UpperLeft), LowerRight(LowerRight) {}
- Point<T> UpperLeft, LowerRight;
- };
- int main() {
- lua_State *myLuaState = lua_open();
- luabind::open(myLuaState);
- // 使用LuaBind導出Point<float>類和Box<float>類
- luabind::module(myLuaState) [
- luabind::class_<Point<float> >("Point")
- .def(luabind::constructor<float, float>())
- .def_readwrite("X", &Point<float>::X)
- .def_readwrite("Y", &Point<float>::Y),
- luabind::class_<Box<float> >("Box")
- .def(luabind::constructor<Point<float>, Point<float> >())
- .def_readwrite("UpperLeft", &Box<float>::UpperLeft)
- .def_readwrite("LowerRight", &Box<float>::LowerRight)
- ];
- // 現在Lua中可以使用爲些類了
- luaL_dostring(
- myLuaState,
- "MyBox = Box(Point(10, 20), Point(30, 40)) "
- "MyBox.UpperLeft.X = MyBox.LowerRight.Y "
- );
- lua_close(myLuaState);
- }
本例中使用def_readwrite定義類成員,我們也可以用def_readonly把類成員定義成只讀。
LuaBind還可以把C++類導出成支持getter和setter的屬性的Lua類:
- struct ResourceManager {
- ResourceManager() :
- m_ResourceCount(0) {}
- void loadResource(const string &sFilename) {
- ++m_ResourceCount;
- }
- size_t getResourceCount() const {
- return m_ResourceCount;
- }
- size_t m_ResourceCount;
- };
- int main() {
- lua_State *myLuaState = lua_open();
- luabind::open(myLuaState);
- // 導出類,在Lua中調用ResourceCount屬性會調用C++中的ResourceManager::getResourceCount
- // 屬性定義有點象C++Builder裏的__property定義,呵呵
- luabind::module(myLuaState) [
- luabind::class_<ResourceManager>("ResourceManager")
- .def("loadResource", &ResourceManager::loadResource)
- .property("ResourceCount", &ResourceManager::getResourceCount)
- ];
- try {
- ResourceManager MyResourceManager;
- // 把MyResourceManager定義成Lua的全局變量
- luabind::globals(myLuaState)["MyResourceManager"] = &MyResourceManager;
- // 調用
- luaL_dostring(
- myLuaState,
- "MyResourceManager:loadResource(/"abc.res/") "
- "MyResourceManager:loadResource(/"xyz.res/") "
- " "
- "ResourceCount = MyResourceManager.ResourceCount "
- );
- // 讀出全局變量
- size_t ResourceCount = luabind::object_cast<size_t>(
- luabind::globals(myLuaState)["ResourceCount"]
- );
- cout << ResourceCount << endl;
- }
- catch(const std::exception &TheError) {
- cerr << TheError.what() << endl;
- }
- lua_close(myLuaState);
- }
附: Lua語法簡介
1.語法約定
Lua語句用分號結尾,不過如果不寫分號,Lua也會自己判斷如何區分每條語句如:
a=1 b=a*2 --這樣寫沒有問題,但不太好看。
建議一行裏有多個語句時用分號隔開
變量名、函數名之類的命名規則與C語言一樣:由字母,下劃線和數字組成,但第一個字符不能是數字。並且不能和Lua的保留字相同。
Lua是大小寫敏感的
使用兩個減號--作爲單行註釋符,多行註釋使用--[[...--]]
2.類型
Lua是動態類型語言,變量不要類型定義。Lua中有8個基本類型分別爲:nil、boolean、number、string、userdata、function、thread和table。同一變量可以隨時改變它的類型,如:
- a = 10 --number
- a = "hello" --string
- a = false --boolean
- a = {10,"hello",false} --table
- a = print --function
nil 所有沒有被賦值過的變量默認值爲nil,給變量賦nil可以收回變量的空間。
boolean 取值false和true。但要注意Lua中所有的值都可以作爲條件。在控制結構的條件中除了false和nil爲假,其他值都爲真。所以Lua認爲0和空串都是真。(注意,和C不一樣哦)
number 表示實數,Lua中沒有整數。不用擔心實數引起的誤差,Lua的numbers可以處理任何長整數。
string 字符串,Lua中的字符串可以存放任何包括0在內的二進制數據。可以使用單引號或雙引號表示字符串,和C一樣使用/作爲轉義符。也可以使用或 [[...]]表示字符串,它可以表示多行,而且不解釋轉義符(也可以是[=[...]=]、[==[]==]、...用於適應各種類型字符串)。另外要注意的是Lua中字符串是不可以修改的。
function 函數,Lua中的函數也可以存儲到變量中,可以作爲其它函數的參數,可以作爲函數的返回值。
table 表,表是Lua特有的功能強大的東東,它是Lua中唯一的一種數據結構,它可以用來描述數組,結構,map的功能。
userdata userdata類型用來將任意C語言數據保存在Lua變量中。例如:用標準I/O庫來描述文件。
thread 線程。由coroutine表創建的一種數據類型,可以實現多線程協同操作。
3.表達式
算術運行符: 加+、減-、乘*、除/、冪^關係運算符:小於<、大於>、小於等於<=、大於等於>=、等於==、不等~=
邏輯運算符:與and、或or、非not
and和or的運算結果返回值是其中的操作數:
a and b -- 如果a爲false,則返回a,否則返回b
a or b -- 如果a爲true,則返回a,否則返回b
所以C中的三元運算符a?b:c在Lua中可以這樣寫:(a and b) or c
連接運算符:連續兩個小數點..,如:
"hello" .. "world" 結果是 "helloworld"
0 .. 1 結果是 "01",當在一個數字後面寫..時,必須加上空格以防止被解釋錯。
取長度操作符:一元操作 #
字符串的長度是它的字節數,table 的長度被定義成一個整數下標 n,它滿足 t[n] 不是 nil 而 t[n+1] 爲 nil。
4.基本語法
賦值
a = a + 1Lua裏的賦值還可以同時給多個變量賦值。變量列表和值列表的各個元素用逗號分開,賦值語句右邊的值會依次賦給左邊的變量。如:
a, b = 10, 2*x --相當於a=10; b=2*x
x, y = y, x --交換x和y
如果賦值符左右個數不同時,Lua會自動丟棄多餘值或以nil補足
局部變量
local i = 10使用local聲明局部變量,局部變量只在所在的代碼塊內有效。
如果不聲明,默認爲全局變量,這個變量在所有Lua環境中有效。
代碼塊是指一個控制結構內,一個函數體,或者一個chunk(變量被聲明的那個文件或者文本串),也可以直接使用do...end(相當於C中的{})。
條件
- if 條件 then
- then-part
- elseif 條件n then
- elseif-part
- .. --->多個elseif
- else
- else-part
- end;
循環
Lua中的循環有:while循環,repeat-until循環,for循環和for in循環。循環中可以用break跳出,Lua語法要求break和return只能是代碼塊的最後一句(放心,正常的代碼都是滿足這個要求的,break和 reuturn後面即使有代碼也是執行不到的,再說了,大不了自己加個do...end好了^_^)
如:
- local i = 1
- while a[i] do
- if a[i] == v then break end
- i = i + 1
- end
while循環
- while condition do
- statements;
- end;
repeat-until循環:
- repeat
- statements;
- until conditions;
for循環
- for var=exp1,exp2,exp3 do
- loop-part
- end
for in循環
- for 變量 in 集合 do
- loop-part
- end
for var_1, ..., var_n in explist do block end
等價於
- do
- local _f, _s, _var = explist
- while true do
- local var_1, ... , var_n = _f(_s, _var)
- _var = var_1
- if _var == nil then break end
- block
- end
- end
- a = {"windows","macos","linux",n=3}
- for k,v in pairs(a) do print(k,v) end
5.函數
- function 函數名 (參數列表)
- statements-list;
- end;
- function foo() return 'a','b','c'; end
- a,b,c = foo()
在Lua中還可以直接定義匿名函數,如
print((function() return 'a','b','c' end)())
更詳細信息請參考<LUA>/doc/manual.html