最近正在學習Lua,先轉一篇個人感覺比較入門的文章

lihz 14年前發布 | 39K 次閱讀 平板 MyBatis 分布式監控
原文轉自www.cppprog.com,由三篇文章組成

Lua是一個嵌入式的腳本語言,它不僅可以單獨使用還能與其它語言混合調用。
Lua
與其它腳本語言相比,其突出優勢在于:

1.  可擴展性。Lua的擴展性非常卓越,以至于很多人把Lua用作搭建領域語言的工具(注:比如游戲腳本)。Lua被設計為易于擴展的,可以通過Lua代碼或者 C代碼擴展,Lua的很多功能都是通過外部庫來擴展的。Lua很容易與C/C++javafortranSmalltalkAda,以及其他語言接口。

2.  簡單。Lua本身簡單,小巧;內容少但功能強大,這使得Lua易于學習,很容易實現一些小的應用。他的完全發布版(代碼、手冊以及某些平臺的二進制文件)僅用一張軟盤就可以裝得下。

3.  高效率。Lua有很高的執行效率,統計表明Lua是目前平均效率最高的腳本語言。

4.  與平臺無關。Lua幾乎可以運行在所有我們聽說過的系統上,如NextStepOS/2PlayStation II (Sony)Mac OS-9OS XBeOSMS-DOSIBM mainframesEPOCPalmOSMCF5206eLITE Evaluation BoardRISC OS,及所有的WindowsUnixLua不是通過使用條件編譯實現平臺無關,而是完全使用ANSI (ISO) C,這意味著只要你有ANSI C編譯器你就可以編譯并使用Lua

要在C++中使用Lua非常簡單,不管是GCC,VC還是C++Builder, 最簡單的方法就是把Lua源碼中除lua.cluac.cprint.c以外的所有c文件與你的代碼一起編譯鏈接(或加入到工程中)即可。
當然,為了方便維護,最好還是先把Lua編譯成庫文件再加入工程。方法如下:

GCC

    直接在Lua所在目錄下make [環境]

    這里的[環境]可以是:aix ansi bsd freebsd generic linux macosx mingw posix solaris

    如果你的環境不在這個列表中,你可以試試ansiposix

 

VC

    在命令行環境下進入Lua所在目錄,執行etc\luavs.bat編譯。

 

C++Builder

    請看我的Blog,如何在C++Builder里編譯Lua

 

頭文件

    因為Lua是用C語言寫的,除非編譯lua庫時指定編譯器強制以C++方式編譯,否則在C++工程中應該這樣包含lua頭文件:

1.  extern "C" {

2.  #include "lua.h"

3.  #include "lualib.h"

4.  #include "lauxlib.h"

5.  }

 

例一,簡單運行Lua代碼

1.  extern "C" {

2.  #include "lua.h"

3.  #include "lualib.h"

4.  #include "lauxlib.h"

5.  }

6.   

7.  #include <iostream>

8.  #include <string>

9.  using namespace std;

10. 

11.int main()

12.{

13.    lua_State *L = lua_open();    //初始化lua

14.    luaL_openlibs(L);    //載入所有lua標準庫

15. 

16.    string s;

17.    while(getline(cin,s))    //cin中讀入一行到s

18.    {

19.        //載入s里的lua代碼后執行

20.        bool err = luaL_loadbuffer(L, s.c_str(), s.length(),

21.                    "line") || lua_pcall(L, 0, 0, 0);

22.        if(err)

23.        {

24.            //如果錯誤,顯示

25.            cerr << lua_tostring(L, -1);

26.            //彈出錯誤信息所在的最上層棧

27.            lua_pop(L, 1);

28.        }

29.    }

30. 

31.    lua_close(L);//關閉

32.    return 0;

33.}


   
這已經是一個功能完備的交互方式Lua解釋器了。

    輸入print "hello world"

    輸出hello world

    輸入for i=1,10 do print(i) end

    輸出從110


   
要調用Lua,首先要使用lua_open(對于5.0以后版本的Lua,建議使用luaL_newstate代替)產生一個lua_State,在使用完后調用lua_close關閉。
   
所有LuaC之間交換的數據都是通過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
里的第二個參數指定要操作的數據處于棧的哪個位置,因為所有的數據只能通過棧來交換,所以很多LuaC API都會要求指定棧的位置。1表示在棧中的第一個元素(也就是第一個被壓入棧的),下一個索引是2,以此類推。我們也可以用棧頂作為參照來存取元素,使用負數:-1指出棧頂元素(也就是最后被壓入的),-2指出它的前一個元素,以此類推。

例二,與Lua交換數據

1.  extern "C" {

2.  #include "lua.h"

3.  #include "lualib.h"

4.  #include "lauxlib.h"

5.  }

6.   

7.  #include <iostream>

8.  #include <string>

9.  using namespace std;

10.    

11.int main()

12.{

13.    //Lua示例代碼

14.    char *szLua_code =

15.        "r = string.gsub(c_Str, c_Mode, c_Tag) --宿主給的變量 "

16.        "u = string.upper(r)";

17.    //Lua的字符串模式

18.    char *szMode = "(%w+)%s*=%s*(%w+)";

19.    //要處理的字符串

20.    char *szStr = "key1 = value1 key2 = value2";

21.    //目標字符串模式

22.    char *szTag = "<%1>%2</%1>";

23. 

24.    lua_State *L = luaL_newstate();

25.    luaL_openlibs(L);

26. 

27.    //把一個數據送給Lua

28.    lua_pushstring(L, szMode);

29.    lua_setglobal(L, "c_Mode");

30.    lua_pushstring(L, szTag);

31.    lua_setglobal(L, "c_Tag");

32.    lua_pushstring(L, szStr);

33.    lua_setglobal(L, "c_Str");

34. 

35.    //執行

36.    bool err = luaL_loadbuffer(L, szLua_code, strlen(szLua_code),

37.                "demo") || lua_pcall(L, 0, 0, 0);

38.    if(err)

39.    {

40.        //如果錯誤,顯示

41.        cerr << lua_tostring(L, -1);

42.        //彈出棧頂的這個錯誤信息

43.        lua_pop(L, 1);

44.    }

45.    else

46.    {

47.        //Lua執行后取得全局變量的值

48.        lua_getglobal(L, "r");

49.        cout << "r = " << lua_tostring(L,-1) << endl;

50.        lua_pop(L, 1);

51.        

52.        lua_getglobal(L, "u");

53.        cout << "u = " << lua_tostring(L,-1) << endl;    

54.        lua_pop(L, 1);

55.    }

56.    lua_close(L);

57.    return 0;

58.}


   
這段代碼把字符串中的key=value字符串全部轉換成XML格式<key>value</key>
   
在這個例子中,C++程序通過調用lua_pushstringC字符串壓入棧頂,lua_setglobal的作用是把棧頂的數據傳到Lua環境中作為全局變量。
   
執行代碼完成后,使用lua_getglobalLua環境中取得全局變量壓入棧頂,然后使用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_pushtablelua_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取得
盡管看起來很牛叉,不過剝開神奇的外衣,實際上Luatable中,所有的數據都是以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
我們把上面的例子改成使用表的

1.  ...

2.  int main()

3.  {

4.      //Lua示例代碼,使用table

5.      char *szLua_code =

6.          "x = {} --用于存放結果的table "

7.          "x[1],x[2] = string.gsub(c.Str, c.Mode, c.Tag) --x[1]里是結果,x[2]里是替換次數 "

8.          "x.u = string.upper(x[1])";

9.      //Lua的字符串模式

10.    char *szMode = "(%w+)%s*=%s*(%w+)";

11.    //要處理的字符串

12.    char *szStr = "key1 = value1 key2 = value2";

13.    //目標字符串模式

14.    char *szTag = "<%1>%2</%1>";

15. 

16.    lua_State *L = luaL_newstate();

17.    luaL_openlibs(L);

18. 

19.    //把一個tabele送給Lua

20.    lua_newtable(L);    //新建一個table并壓入棧頂

21.    lua_pushstring(L, "Mode");// key

22.    lua_pushstring(L, szMode);// value

23.    //設置newtable[Mode]=szMode

24.    //由于上面兩次壓棧,現在table元素排在棧頂往下數第三的位置

25.    lua_settable(L, -3);

26.    //lua_settable會自己彈出上面壓入的keyvalue

27. 

28.    lua_pushstring(L, "Tag");// key

29.    lua_pushstring(L, szTag);// value

30.    lua_settable(L, -3);    //設置newtable[Tag]=szTag

31. 

32.    lua_pushstring(L, "Str");// key

33.    lua_pushstring(L, szStr);// value

34.    lua_settable(L, -3);    //設置newtable[Str]=szStr

35. 

36.    lua_setglobal(L,"c"); //將棧頂元素(newtable)置為Lua中的全局變量c

37. 

38.    //執行

39.    bool err = luaL_loadbuffer(L, szLua_code, strlen(szLua_code),

40.                "demo") || lua_pcall(L, 0, 0, 0);

41.    if(err)

42.    {

43.        //如果錯誤,顯示

44.        cerr << lua_tostring(L, -1);

45.        //彈出棧頂的這個錯誤信息

46.        lua_pop(L, 1);

47.    }

48.    else

49.    {

50.        //Lua執行后取得全局變量的值

51.        lua_getglobal(L, "x");

52. 

53.        //這個x應該是個table

54.        if(lua_istable(L,-1))

55.        {

56.            //取得x.u,x["u"]

57.            lua_pushstring(L,"u");    //key

58.            //由于這次壓棧,x處于棧頂第二位置

59.            lua_gettable(L,-2);

60.            //lua_gettable會彈出上面壓入的key,然后把對應的value壓入

61.            //取得數據,然后從棧中彈出這個value

62.            cout << "x.u = " << lua_tostring(L,-1) << endl;

63.            lua_pop(L, 1);

64.            

65.            //取得x[1]x[2]

66.            for(int i=1; i<=2; i++)

67.            {

68.                //除了key是數字外,與上面的沒什么區別

69.                lua_pushnumber(L,i);

70.                lua_gettable(L,-2);

71.                cout << "x[" << i <<"] = " << lua_tostring(L,-1) << endl;

72.                lua_pop(L, 1);

73.            }

74.        }

75. 

76.        //彈出棧頂的x

77.        lua_pop(L, 1);

78.    }

79.    lua_close(L);

80.    return 0;

81.}

本例中用到的新Lua C API是:

void lua_newtable (lua_State *L);

    新建一個空的table并壓入棧頂。

void lua_settable (lua_State *L, int idx);

    lua_settabletable在棧中的索引作為參數,并將棧頂的keyvalue出棧,用這兩個值修改table

void lua_gettable (lua_State *L, int idx);

    lua_gettabletable在棧中的索引作為參數,彈出棧頂的元素作為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來取得函數,剩下的問題只是怎樣執行它(函數元素)的問題了。

1.  ...

2.  int main()

3.  {

4.      //Lua示例代碼,是一個函數

5.      char *szLua_code =

6.          "function gsub(Str, Mode, Tag)"

7.          "    a,b = string.gsub(Str, Mode, Tag) "

8.          "    c = string.upper(a) "

9.          "    return a,b,c --多個返回值 "

10.        "end";

11.    //Lua的字符串模式

12.    char *szMode = "(%w+)%s*=%s*(%w+)";

13.    //要處理的字符串

14.    char *szStr = "key1 = value1 key2 = value2";

15.    //目標字符串模式

16.    char *szTag = "<%1>%2</%1>";

17. 

18.    lua_State *L = luaL_newstate();

19.    luaL_openlibs(L);

20. 

21.    //執行

22.    bool err = luaL_loadbuffer(L, szLua_code, strlen(szLua_code),

23.                "demo") || lua_pcall(L, 0, 0, 0);

24.    if(err)

25.    {

26.        cerr << lua_tostring(L, -1);

27.        lua_pop(L, 1);

28.    }

29.    else

30.    {

31.        //Lua執行后取得全局變量的值

32.        lua_getglobal(L, "gsub");

33.        if(lua_isfunction(L,-1))    //確認一下是個函數

34.        {

35.            //依次放入三個參數

36.            lua_pushstring(L,szStr);

37.            lua_pushstring(L,szMode);

38.            lua_pushstring(L,szTag);

39.            //調用,我們有3個參數,要得到2個結果

40.            //你可能注意到gsub函數返回了3個,不過我們只要2個,這沒有問題

41.            //沒有使用錯誤處理回調,所以lua_pcall最后一個參數是0

42.            if(0 != lua_pcall(L, 3, 2, 0))

43.            {

44.                //如果錯誤,顯示

45.                cerr << lua_tostring(L, -1);

46.                lua_pop(L, 1);                

47.            }

48.            else

49.            {

50.                //正確,得到兩個結果,注意在棧里的順序

51.                cout << "a = " << lua_tostring(L, -2) << endl;

52.                cout << "b = " << lua_tostring(L, -1) << endl;

53.                //彈出這兩個結果

54.                lua_pop(L, 2);

55.            }

56.        }

57.        else

58.        {

59.            lua_pop(L,1);

60.        }

61.    }

62.    lua_close(L);

63.    return 0;

64.}


   
調用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 作為出錯信息返回在堆棧上。

 

 

 

 

 

 

 

 

 

 

 

閉包和偽索引

http://www.cppprog.com/2009/0210/63.html

例四,在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的位置。

1.  ...

2.  #include <complex> //復數

3.   

4.  //C函數,做復數計算,輸入實部,虛部。輸出絕對值和角度

5.  int calcComplex(lua_State *L)

6.  {

7.      //從棧中讀入實部,虛部

8.      double r = luaL_checknumber(L,1);

9.      double i = luaL_checknumber(L,2);

10.    complex<double> c(r,i);

11.    //存入絕對值

12.    lua_pushnumber(L,abs(c));

13.    //存入角度

14.    lua_pushnumber(L,arg(c)*180.0/3.14159);

15.    return 2;//兩個結果

16.}

17. 

18.int main()

19.{

20.    char *szLua_code =

21.        "v,a = CalcComplex(3,4) "

22.        "print(v,a)";

23. 

24.    lua_State *L = luaL_newstate();

25.    luaL_openlibs(L);

26.   

27.    //放入C函數

28.    lua_pushcfunction(L, calcComplex);

29.    lua_setglobal(L, "CalcComplex");

30.   

31.    //執行

32.    bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);

33.    if(err)

34.    {

35.        cerr << lua_tostring(L, -1);

36.        lua_pop(L, 1);

37.    }

38. 

39.    lua_close(L);

40.    return 0;

41.}

    結果返回5 53.13...,和其它數據一樣,給Lua代碼提供C函數也是通過棧來操作的,因為lua_pushcfunctionlua_setglobal的 組合很常用,所以Lua提供了一個宏:
    #define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n)))
   
這兩句代碼也就可寫成lua_register(L,"CalcComplex",calcComplex);
   

閉包(closure)

    在編寫用于LuaC函數時,我們可能需要一些類似于面向對象的能力,比如我們想在Lua中使用象這樣的一個計數器類:

1.  struct CCounter{

2.      CCounter()

3.          :m_(0){}

4.      int count(){

5.          return ++i;

6.      }

7.  private:

8.      int m_;

9.  };

    這里如果我們僅僅使用lua_pushcfunction提供一個count函數已經不能滿足要求(使用static? 不行,這樣就不能同時使用多個計數器,并且如果程序中有多個Lua環境的話它也不能工作)
   
這時我們就需要一種機制讓數據與某個函數關聯,形成一個整體,這就是Lua中的閉包,而閉包里與函數關聯的數據稱為UpValue
   
使用Lua閉包的方法是定義一個工廠函數,由它來指定UpValue的初值和對應的函數,如:

1.  ...

2.  //計算函數

3.  int count(lua_State *L)

4.  {

5.      //得到UpValue

6.      double m_ = lua_tonumber(L, lua_upvalueindex(1));

7.      //更改UpValue

8.      lua_pushnumber(L, ++m_);

9.      lua_replace(L, lua_upvalueindex(1));

10.    //返回結果(直接復制一份UpValue作為結果)

11.    lua_pushvalue(L, lua_upvalueindex(1));

12.    return 1; 

13.}

14.//工廠函數,把一個數字和count函數關聯打包后返回閉包。

15.int newCount(lua_State *L)

16.{

17.    //計數器初值(即UpValue

18.    lua_pushnumber(L,0);

19.    //放入計算函數,告訴它與這個函數相關聯的數據個數

20.    lua_pushcclosure(L, count, 1);

21.    return 1;//一個結果,即函數體

22.}

23. 

24.int main()

25.{

26.    char *szLua_code =

27.        "c1 = NewCount() "

28.        "c2 = NewCount() "

29.        "for i=1,5 do print(c1()) end "

30.        "for i=1,5 do print(c2()) end";

31. 

32.    lua_State *L = luaL_newstate();

33.    luaL_openlibs(L);

34.   

35.    //放入C函數

36.    lua_register(L,"NewCount",newCount);

37.   

38.    //執行

39.    bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);

40.    if(err)

41.    {

42.        cerr << lua_tostring(L, -1);

43.        lua_pop(L, 1);

44.    }

45. 

46.    lua_close(L);

47.    return 0;

48.}


   
執行結果是:

    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_GLOBALSINDEXLUA_REGISTRYINDEX,這兩個偽索引處的數據是table類型的。
    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得到所有的全局變量

1.  int main()

2.  {

3.      char *szLua_code =

4.          "a=10 "

5.          "b=\"hello\" "

6.          "c=true";

7.   

8.      lua_State *L = luaL_newstate();

9.      luaL_openlibs(L);

10.   

11.    //執行

12.    bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);

13.    if(err)

14.    {

15.        cerr << lua_tostring(L, -1);

16.        lua_pop(L, 1);

17.    }

18.    else

19.    {

20.        //遍歷LUA_GLOBALSINDEX所在的table得到

21.        lua_pushnil(L);

22.        while(0 != lua_next(L,LUA_GLOBALSINDEX))

23.        {

24.            // 'key' (在索引 -2 處)  'value' (在索引 -1 處)

25.            /*

26.            在遍歷一張表的時候,不要直接對 key 調用 lua_tolstring 

27.            除非你知道這個 key 一定是一個字符串。

28.            調用 lua_tolstring 有可能改變給定索引位置的值;

29.            這會對下一次調用 lua_next 造成影響。

30.            所以復制一個key到棧頂先

31.            */

32.            lua_pushvalue(L, -2);

33.            printf("%s - %s ",

34.                  lua_tostring(L, -1),    //key,剛才復制的

35.                  lua_typename(L, lua_type(L,-2))); //value,現在排在-2的位置了

36.            // 移除 'value' 和復制的key;保留源 'key' 做下一次疊代

37.            lua_pop(L, 2);

38.        }

39.    }

40.    lua_close(L);

41.    return 0;

42.}


    LUA_REGISTRYINDEX
偽索引處也存放著一個table,它就是Lua注冊表(registry)。這個注冊表可以用來保存任何C代碼想保存 的Lua值。
   
加入到注冊表里的數據相當于全局變量,不過只有C代碼可以存取而Lua代碼不能。因此用它來存儲函數庫(在下一節介紹)中的一些公共變量再好不過了。

 

   一個Lua庫實際上是一個定義了一系列Lua函數的代碼塊,并將這些函數保存在適當的地方,通常作為table的域來保存。LuaC庫就是這樣實現的。
   
作為一個完整的庫,我們還需要寫一個函數來負責把庫中的所有公共函數放到table里,然后注冊到Lua全局變量里,就像luaopen_*做的一樣。 Lua為這種需求提供了輔助函數luaL_register,它接受一個C函數的列表和他們對應的函數名,并且作為一個庫在一個table中注冊所有這些函數。
下例中注冊了一個名為Files的庫,定義了三個庫函數:FindFirst,FindNext,FindClose

1.  extern "C" {

2.  #include "lua.h"

3.  #include "lualib.h"

4.  #include "lauxlib.h"

5.  }

6.   

7.  #include <iostream>

8.  #include <string>

9.  #include <windows.h>

10.using namespace std;

11. 

12.//函數庫示例,Windows下查找文件功能

13.//輸入:string路徑名

14.//輸出:userdata存放Handle(如果沒找到,則是nil), string文件名

15.int findfirst( lua_State *L )

16.{

17.    WIN32_FIND_DATAA FindFileData;

18.    HANDLE hFind = ::FindFirstFileA(luaL_checkstring(L,1), &FindFileData);

19.   

20.    if(INVALID_HANDLE_VALUE == hFind)

21.        lua_pushnil(L);

22.    else

23.        lua_pushlightuserdata(L, hFind);

24. 

25.    lua_pushstring(L, FindFileData.cFileName);

26.   

27.    return 2;

28.}

29. 

30.//輸入:userdata:findfirst返回的Handle

31.//輸出:string:文件名,如果沒找到,則返回nil

32.int findnext( lua_State *L )

33.{

34.    WIN32_FIND_DATAA FindFileData;

35.    if(::FindNextFileA(lua_touserdata(L,1),&FindFileData))

36.        lua_pushstring(L, FindFileData.cFileName);

37.    else

38.        lua_pushnil(L);

39.    return 1;

40.}

41. 

42.//輸入:userdata:findfirst返回的Handle

43.//沒有輸出

44.int findclose( lua_State *L )

45.{

46.    ::FindClose(lua_touserdata(L,1));

47.    return 0;

48.}

49. 

50.//注冊函數庫

51.static const struct luaL_reg lrFiles [] = {

52.    {"FindFirst", findfirst},

53.    {"FindNext", findnext},

54.    {"FindClose", findclose},

55.    {NULL, NULL}    /* sentinel */

56.};

57.int luaopen_Files (lua_State *L) {

58.    luaL_register(L, "Files", lrFiles);

59.    return 1;

60.}

61. 

62.int main()

63.{

64.    char* szLua_code=

65.        "hFind,sFile = Files.FindFirst('c:\\\\*.*'); "

66.        "if hFind then "

67.        "    repeat "

68.        "        print(sFile) "

69.        "        sFile = Files.FindNext(hFind) "

70.        "    until sFile==nil; "

71.        "    Files.FindClose(hFind) "

72.        "end";

73.    lua_State *L = luaL_newstate();

74.    luaL_openlibs(L);

75.    luaopen_Files(L);

76. 

77.    bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);

78.    if(err)

79.    {

80.        cerr << lua_tostring(L, -1);

81.        lua_pop(L, 1);

82.    }

83.    lua_close(L);

84.    return 0;

85.}


   
本例運行結果是顯示出C盤根目錄下所有的文件名。
    Lua
官方建議把函數庫寫進動態鏈接庫中(windows.dll文件,linux.so文件),這樣就可以在Lua代碼中使用loadlib函數動 態載入函數庫
例如,我們把上面的例子改成動態鏈接庫版本:
DLL
代碼,假定生成的文件名為fileslib.dll

1.  extern "C" {

2.  #include "lua.h"

3.  #include "lualib.h"

4.  #include "lauxlib.h"

5.  }

6.  #include <windows.h>

7.   

8.  BOOL APIENTRY DllMain( HMODULE hModule,

9.                         DWORD  ul_reason_for_call,

10.                       LPVOID lpReserved

11.                     )

12.{

13.    return TRUE;

14.}

15. 

16.//函數庫示例,Windows下查找文件功能

17.//輸入:string路徑名

18.//輸出:userdata存放Handle(如果沒找到,則是nil), string文件名

19.int findfirst( lua_State *L )

20.{

21.    WIN32_FIND_DATAA FindFileData;

22.    HANDLE hFind = ::FindFirstFileA(luaL_checkstring(L,1), &FindFileData);

23.   

24.    if(INVALID_HANDLE_VALUE == hFind)

25.        lua_pushnil(L);

26.    else

27.        lua_pushlightuserdata(L, hFind);

28. 

29.    lua_pushstring(L, FindFileData.cFileName);

30.   

31.    return 2;

32.}

33. 

34.//輸入:userdata:findfirst返回的Handle

35.//輸出:string:文件名,如果沒找到,則返回nil

36.int findnext( lua_State *L )

37.{

38.    WIN32_FIND_DATAA FindFileData;

39.    if(::FindNextFileA(lua_touserdata(L,1),&FindFileData))

40.        lua_pushstring(L, FindFileData.cFileName);

41.    else

42.        lua_pushnil(L);

43.    return 1;

44.}

45. 

46.//輸入:userdata:findfirst返回的Handle

47.//沒有輸出

48.int findclose( lua_State *L )

49.{

50.    ::FindClose(lua_touserdata(L,1));

51.    return 0;

52.}

53. 

54.//注冊函數庫

55.static const struct luaL_reg lrFiles [] = {

56.    {"FindFirst", findfirst},

57.    {"FindNext", findnext},

58.    {"FindClose", findclose},

59.    {NULL, NULL}    /* sentinel */

60.};

61.//導出,注意原型為typedef int (*lua_CFunction) (lua_State *L);

62.extern "C"    __declspec(dllexportint luaopen_Files (lua_State *L) {

63.    luaL_register(L, "Files", lrFiles);

64.    return 1;

65.}

Lua調用代碼(或者直接使用Lua.exe調用,dll文件必須處于package.cpath指定的目錄中,默認與執行文件在同一目錄即可):

1.  extern "C" {

2.  #include "lua.h"

3.  #include "lualib.h"

4.  #include "lauxlib.h"

5.  }

6.   

7.  #include <iostream>

8.  #include <string>

9.  #include <windows.h>

10.using namespace std;

11. 

12.int main()

13.{

14.    char* szLua_code=

15.        "fileslib = package.loadlib('fileslib.dll', 'luaopen_Files') "

16.        "fileslib() "

17.        "hFind,sFile = Files.FindFirst('c:\\\\*.*'); "

18.        "if hFind then "

19.        "    repeat "

20.        "        print(sFile) "

21.        "        sFile = Files.FindNext(hFind) "

22.        "    until sFile==nil; "

23.        "    Files.FindClose(hFind) "

24.        "end";

25.    lua_State *L = luaL_newstate();

26.    luaL_openlibs(L);

27. 

28.    bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);

29.    if(err)

30.    {

31.        cerr << lua_tostring(L, -1);

32.        lua_pop(L, 1);

33.    }

34.    lua_close(L);

35.    return 0;

36.}


Lua
代碼里使用package.loadlib得到動態鏈接庫中的luaopen_Files函數,然后調用它注冊到Lua中,如果動態鏈接庫中的導出 函數名稱滿足luaopen_<庫名>的話,還可以使用require直接載入。
比如,如果把本例中的DLL代碼里的導出函數名luaopen_Files改成luaopen_fileslib的話,Lua代碼便可以改成:

1.  char* szLua_code=

2.          "require('fileslib') "

3.          "hFind,sFile = Files.FindFirst('c:\\\\*.*'); "

4.          ...

例五,與Lua交換自定義數據

    由于Lua中的數據類型遠不能滿足C語言的需要,為此Lua提供了userdata,一個userdata提供了一個在Lua中沒有預定義操作的raw內 存區域。
   
在例四的函數庫代碼中我們已經使用過lightuserdata,它是userdata的一個特例:一個表示C指針的值(也就是一個void *類型的值)。
   
下面的例子我們使用userdata來給Lua提供一個窗體類用于建立,顯示窗體。為了簡化窗體控制代碼,在C函數中我們使用了C++BuilderVCL,所以下面的代碼要在C++Builder下編譯才能通過。當然,稍微修改一下也可以使用MFC,QT,wxWidget等來代替。

1.  //---------------------------------------------------------------------------

2.  #include <vcl.h>

3.  extern "C" {

4.  #include "lua.h"

5.  #include "lualib.h"

6.  #include "lauxlib.h"

7.  }

8.   

9.  #include <iostream>

10.#pragma hdrstop

11.//---------------------------------------------------------------------------

12.#pragma argsused

13. 

14.typedef TWinControl* PWinControl;

15.//創建窗體,輸入父窗體(或nil),類型,標題

16.//輸出創建后的窗體

17.int newCtrl(lua_State *L)

18.{

19.    //input:TWinControl *Parent, type(TForm,TButton,TEdit), text(optional)

20.    TWinControl *Parent = NULL;

21.    //userdata中取得TWinControl*

22.    if(lua_isuserdata(L,1))

23.        Parent = *(PWinControl*)lua_touserdata(L,1);

24.    String Type = UpperCase(luaL_checkstring(L, 2));

25.    String Text = lua_tostring(L, 3);

26. 

27.    TWinControl *R = NULL;

28. 

29.    if(Type == "FORM")

30.    {

31.        R = new TForm(Application);

32.    }

33.    else if(Type == "BUTTON")

34.    {

35.        R = new TButton(Application);

36.    }

37.    else if(Type == "EDIT")

38.    {

39.        R = new TEdit(Application);

40.    }

41.    else

42.    {

43.        luaL_error(L, "unknow type!");

44.    }

45. 

46.    if(Parent)

47.        R->Parent = Parent;

48. 

49.    if(!Text.IsEmpty())

50.        ::SetWindowText(R->Handle, Text.c_str());

51. 

52.    //新建userdata,大小為sizeof(PWinControl),用于存放上面生成的窗體指針

53.    PWinControl* pCtrl = (PWinControl*)lua_newuserdata(L,sizeof(PWinControl));

54.    *pCtrl = R;

55.    return 1;

56.}

57. 

58.//顯示窗體

59.int showCtrl(lua_State *L)

60.{

61.    //input: TWinControl*, for TForm, use ShowModal

62.    TWinControl* Ctrl = *(PWinControl*)lua_touserdata(L,1);

63.    TForm *fm = dynamic_cast<TForm*>(Ctrl);

64.    if(fm)

65.        fm->ShowModal();

66.    else

67.        Ctrl->Show();

68.    return 0;

69.}

70. 

71.//定位窗體,輸入窗體,左,上,右,下

72.int posCtrl(lua_State *L)

73.{

74.    //input: TWinControl*, Left, Top, Right, Bottom

75.    TWinControl* Ctrl = *(PWinControl*)lua_touserdata(L,1);

76.    Ctrl->BoundsRect = TRect(

77.        luaL_checkint(L, 2),

78.        luaL_checkint(L, 3),

79.        luaL_checkint(L, 4),

80.        luaL_checkint(L, 5));

81. 

82.    return 0;

83.}

84. 

85.//刪除窗體

86.int delCtrl(lua_State *L)

87.{

88.    //input: TWinControl*

89.    TWinControl* Ctrl = *(PWinControl*)lua_touserdata(L,1);

90.    delete Ctrl;

91.    return 0;

92.}

93. 

94.//把這些函數作為VCL函數庫提供給Lua

95.static const struct luaL_reg lib_VCL [] = {

96.    {"new", newCtrl},

97.    {"del", delCtrl},

98.    {"pos", posCtrl},

99.    {"show", showCtrl},

100.       {NULL, NULL}

101.   };

102.    

103.   int luaopen_VCL (lua_State *L) {

104.       luaL_register(L, "VCL", lib_VCL);

105.       return 1;

106.   }

107.    

108.   int main(int argc, char* argv[])

109.   {

110.       char* szLua_code=

111.           "fm = VCL.new(nil,'Form','Lua Demo'); "    //新建主窗體fm

112.           "VCL.pos(fm, 200, 200, 500, 300); "        //定位

113.           "edt = VCL.new(fm, 'Edit', 'Hello World'); "  //fm上建立一個編輯框edt

114.           "VCL.pos(edt, 5, 5, 280, 28); "

115.           "btn = VCL.new(fm, 'Button', 'Haha'); "    //fm上建立一個按鈕btn

116.           "VCL.pos(btn, 100, 40, 150, 63); "

117.           "VCL.show(edt); "

118.           "VCL.show(btn); "

119.           "VCL.show(fm); "                       //顯示

120.           "VCL.del(fm);";                         //刪除

121.    

122.       lua_State *L = luaL_newstate();

123.       luaL_openlibs(L);

124.       luaopen_VCL(L);

125.    

126.       bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);

127.       if(err)

128.       {

129.           std::cerr << lua_tostring(L, -1);

130.           lua_pop(L, 1);

131.       }   

132.    

133.       lua_close(L);

134.       return 0;

135.   }

136.   //---------------------------------------------------------------------------

 

使用metatable提供面向對象調用方式

    上面的VCL代碼庫為Lua提供了GUI的支持,但是看那些Lua代碼,還處于面向過程時期。如何能把VCL.show(edt)之類的代碼改成edt: show()這樣的形式呢?還是先看代碼:

1.  //---------------------------------------------------------------------------

2.  #include <vcl.h>

3.  extern "C" {

4.  #include "lua.h"

5.  #include "lualib.h"

6.  #include "lauxlib.h"

7.  }

8.   

9.  #include <iostream>

10.#pragma hdrstop

11.//---------------------------------------------------------------------------

12.#pragma argsused

13. 

14.typedef TWinControl* PWinControl;

15.//創建窗體,輸入父窗體(或nil),類型,標題

16.//輸出創建后的窗體

17.int newCtrl(lua_State *L)

18.{

19.    //input:TWinControl *Parent, type(TForm,TButton,TEdit), text(optional)

20.    TWinControl *Parent = NULL;

21. 

22.    if(lua_isuserdata(L,1))

23.        Parent = *(PWinControl*)luaL_checkudata(L,1,"My_VCL");

24.    String Type = UpperCase(luaL_checkstring(L, 2));

25.    String Text = lua_tostring(L, 3);

26. 

27.    TWinControl *R = NULL;

28. 

29.    if(Type == "FORM")

30.        R = new TForm(Application);

31.    else if(Type == "BUTTON")

32.        R = new TButton(Application);

33.    else if(Type == "EDIT")

34.        R = new TEdit(Application);

35.    else

36.        luaL_error(L, "unknow type!");

37. 

38.    if(Parent)

39.        R->Parent = Parent;

40. 

41.    if(!Text.IsEmpty())

42.        ::SetWindowText(R->Handle, Text.c_str());

43. 

44.    //output TWinControl*

45.    PWinControl* pCtrl = (PWinControl*)lua_newuserdata(L,sizeof(PWinControl));

46.    *pCtrl = R;

47.    //關聯metatable

48.    luaL_getmetatable(L, "My_VCL");

49.    lua_setmetatable(L, -2);

50.    return 1;

51.}

52. 

53.//顯示窗體

54.int showCtrl(lua_State *L)

55.{

56.    //input: TWinControl*, for TForm, use ShowModal

57.    TWinControl* Ctrl = *(PWinControl*)luaL_checkudata(L,1,"My_VCL");

58.    TForm *fm = dynamic_cast<TForm*>(Ctrl);

59.    if(fm)

60.        fm->ShowModal();

61.    else

62.        Ctrl->Show();

63.    return 0;

64.}

65. 

66.//定位窗體,輸入窗體,左,上,右,下

67.int posCtrl(lua_State *L)

68.{

69.    //input: TWinControl*, Left, Top, Right, Bottom

70.    TWinControl* Ctrl = *(PWinControl*)luaL_checkudata(L,1,"My_VCL");

71.    Ctrl->BoundsRect = TRect(

72.        luaL_checkint(L, 2),

73.        luaL_checkint(L, 3),

74.        luaL_checkint(L, 4),

75.        luaL_checkint(L, 5));

76. 

77.    return 0;

78.}

79. 

80.//刪除窗體

81.int delCtrl(lua_State *L)

82.{

83.    //input: TWinControl*

84.    TWinControl* Ctrl = *(PWinControl*)luaL_checkudata(L,1,"My_VCL");

85.    delete Ctrl;

86.    return 0;

87.}

88. 

89.//把這些函數作為VCL函數庫提供給Lua

90.static const struct luaL_reg lib_VCL [] = {

91.    {"new", newCtrl},

92.    {"del", delCtrl},

93.    {"pos", posCtrl},

94.    {"show", showCtrl},

95.    {NULL, NULL}

96.};

97. 

98.int luaopen_VCL (lua_State *L) {

99.    //建立metatable

100.       luaL_newmetatable(L, "My_VCL");

101.    

102.       //查找索引,把它指向metatable自身(因為稍后我們會在metatable里加入一些成員)

103.       lua_pushvalue(L, -1);

104.       lua_setfield(L,-2,"__index");

105.    

106.       //pos方法

107.       lua_pushcfunction(L, posCtrl);

108.       lua_setfield(L,-2,"pos");

109.    

110.       //show方法

111.       lua_pushcfunction(L, showCtrl);

112.       lua_setfield(L,-2,"show");

113.    

114.       //析構,如果表里有__gc,Lua的垃圾回收機制會調用它。

115.       lua_pushcfunction(L, delCtrl);

116.       lua_setfield(L,-2,"__gc");

117.    

118.       luaL_register(L, "VCL", lib_VCL);

119.       return 1;

120.   }

121.    

122.   int main(int argc, char* argv[])

123.   {

124.       char* szLua_code=

125.           "local fm = VCL.new(nil,'Form','Lua Demo'); "    //新建主窗體fm

126.           "fm:pos(200, 200, 500, 300); "        //定位

127.           "local edt = VCL.new(fm, 'Edit', 'Hello World'); "  //fm上建立一個編輯框edt

128.           "edt:pos(5, 5, 280, 28); "

129.           "local btn = VCL.new(fm, 'Button', 'Haha'); "    //fm上建立一個按鈕btn

130.           "btn:pos(100, 40, 150, 63); "

131.           "edt:show(); "

132.           "btn:show(); "

133.           "fm:show(); ";                       //顯示

134.           //"VCL.del(fm);";   //不再需要刪除了,Lua的垃圾回收在回收userdata地會調用metatable.__gc

135.    

136.       lua_State *L = luaL_newstate();

137.       luaL_openlibs(L);

138.       luaopen_VCL(L);

139.    

140.       bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);

141.       if(err)

142.       {

143.           std::cerr << lua_tostring(L, -1);

144.           lua_pop(L, 1);

145.       }   

146.    

147.       lua_close(L);

148.       return 0;

149.   }

150.   //---------------------------------------------------------------------------


我們這兒用到的輔助函數有:

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);

    檢查在棧中指定位置的對象是否為帶有給定名字的metatableuserdata

   
   
我們只改動了luaopen_VCLnewCtrl函數。
   
luaopen_VCL里,我們建立了一個metatable,然后讓它的__index成員指向自身,并加入了pos,show函數成員和__gc函 數成員。
   
newCtrl里,我們把luaopen_VCL里建立的metatable和新建的userdata關聯,于是:

·         userdata的索引操作就會轉向metatable.__index

·         因為metatable.__indexmetatable自身,所以就在這個metatable里查找

·         這樣,對userdataposshow索引轉到metatable里的posshow上,它們指向的是我們的C函數posCtrl posShow

·         最后,當Lua回收這些userdata前,會調用metatable.__gc(如果有的話),我們已經把metatable.__gc指向了C函數 delCtrl

加入metatable后,我們還得到了額外的好處:可以區分不同的userdata以保證類型安全,我們把所以的lua_touserdata改成了luaL_checkudata
   
關于metatable的知識已超出本文討論范圍,請參考Lua官方手冊。

 

 

 

C++中使用Lua()

http://www.cppprog.com/2009/0211/64.html

例六,使用C++包裝類

    盡管用LuaC API已經可以方便地寫出與Lua交互的程序了,不過對于用慣C++的人來說還是更愿意用C++的方式來解決問題。于是開源社區就出現了不少Lua C APIC++wrap,比如:LuaBind,LuaPlus,toLua
   
這里介紹的是LuaBind庫,
下載
   
它在Windows下貌似只能支持MSVCICC,好在Lua可以支持動態庫的載入,所以用VC+LuaBindLua庫,再用C++Builder調用也是個好主意。
   
VC使用LuaBind的方法是把LuaBindsrc目錄下的cpp文件加入工程(當然也可以先編譯成靜態庫),加入Lua庫,設置LuaBind,LuaBoost的頭文件路徑。

頭文件:

1.  //Lua頭文件

2.  extern "C"

3.  {

4.  #include <lua.h>

5.  #include <lualib.h>

6.  #include <lauxlib.h>

7.  }

8.  //LuaBind頭文件

9.  #include <luabind/luabind.hpp> 

 

C++中調用Lua函數

    調用Lua函數那是最簡單不過的事情了,用LuaBindcall_function()模板函數就可以了:

1.  int main(

2.    // 建立新的Lua環境

3.    lua_State *myLuaState = luaL_newstate();

4.   

5.    // LuaBind“認識這個Lua環境

6.    luabind::open(myLuaState);

7.   

8.    // 定義一個叫addLua函數

9.    luaL_dostring(

10.    myLuaState,

11.    "function add(first, second) "

12.    "  return first + second "

13.    "end "

14.  );

15.   

16.  //調用add函數

17.  cout << "Result: "

18.       << luabind::call_function<int>(myLuaState, "add", 2, 3)

19.       << endl;

20. 

21.  lua_close(myLuaState);

22.}


在本例中我們先使用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腳本調用:

1.  void print_hello(int number) {

2.    cout << "hello world " << number << endl;

3.  }

4.   

5.  int main(

6.    // 建立新的Lua環境

7.    lua_State *myLuaState = lua_open();

8.   

9.    // LuaBind“認識這個Lua環境

10.  luabind::open(myLuaState);

11. 

12.  // 添加print_hello函數到Lua環境中

13.  luabind::module(myLuaState) [

14.    luabind::def("print_hello", print_hello)

15.  ];

16. 

17.  // 現在Lua中可以調用print_hello

18.  luaL_dostring(

19.    myLuaState,

20.    "print_hello(123) "

21.  );

22. 

23.  lua_close(myLuaState);

24.}


   
Lua環境加入函數或其它東東的方法是:

  luabind::module(lua_State* L, char const* name = 0) [

    ...

  ];

    其中module函數中的第二個指定要加入的東東所處的名空間(其實就是table),如果為0,則處于全局域之下。
   
在中括號里的luabind::defprint_hello函數提供給Lua環境,第一個參數是Lua中使用的函數名。
   
如果要定義多個函數,可以使用逗號分隔。

Lua代碼中使用C++

    如果我們直接使用Lua C APILua腳本注冊一個C++類,一般是使用userdata+metatable的方法,就象我們在例五中做的一樣。這樣做盡管難度不大,卻非常繁瑣而且不方便維護。
   
使用LuaBind我們就可以更方便地向Lua腳本注冊C++類了,例:

1.  class NumberPrinter {

2.    public:

3.      NumberPrinter(int number) :

4.        m_number(number) {}

5.   

6.      void print() {

7.        cout << m_number << endl;

8.      }

9.   

10.  private:

11.    int m_number;

12.};

13. 

14.int main() {

15.  lua_State *myLuaState = lua_open();

16.  luabind::open(myLuaState);

17. 

18.  // 使用LuaBind導出NumberPrinter

19.  luabind::module(myLuaState) [

20.    luabind::class_<NumberPrinter>("NumberPrinter")

21.      .def(luabind::constructor<int>())

22.      .def("print", &NumberPrinter::print)

23.  ];

24. 

25.  // 現在Lua中可以使用NumberPinter類了

26.  luaL_dostring(

27.    myLuaState,

28.    "Print2000 = NumberPrinter(2000) "

29.    "Print2000:print() "

30.  );

31. 

32.  lua_close(myLuaState);

33.}


為了注冊一個類,LuaBind提供了class_類。它有一個重載過的成員函數 def() 。這個函數被用來注冊類的成員函數、操作符、構造器、枚舉和屬性。
它將返回this指針,這樣我們就可以方便地直接注冊更多的成員。

屬性

LuaBind 也可以導出類成員變量:

1.  template<typename T>

2.  struct Point {

3.    Point(T X, T Y) :

4.      X(X), Y(Y) {}

5.   

6.    T X, Y;

7.  };

8.   

9.  template<typename T>

10.struct Box {

11.  Box(Point<T> UpperLeft, Point<T> LowerRight) :

12.    UpperLeft(UpperLeft), LowerRight(LowerRight) {}

13. 

14.  Point<T> UpperLeft, LowerRight;

15.};

16. 

17.int main() {

18.  lua_State *myLuaState = lua_open();

19.  luabind::open(myLuaState);

20. 

21.  // 使用LuaBind導出Point<float>類和Box<float>

22.  luabind::module(myLuaState) [

23.    luabind::class_<Point<float> >("Point")

24.      .def(luabind::constructor<floatfloat>())

25.      .def_readwrite("X", &Point<float>::X)

26.      .def_readwrite("Y", &Point<float>::Y),

27. 

28.    luabind::class_<Box<float> >("Box")

29.      .def(luabind::constructor<Point<float>, Point<float> >())

30.      .def_readwrite("UpperLeft", &Box<float>::UpperLeft)

31.      .def_readwrite("LowerRight", &Box<float>::LowerRight)

32.  ];

33. 

34.  // 現在Lua中可以使用為些類了

35.  luaL_dostring(

36.    myLuaState,

37.    "MyBox = Box(Point(10, 20), Point(30, 40)) "

38.    "MyBox.UpperLeft.X = MyBox.LowerRight.Y "

39.  );

40. 

41.  lua_close(myLuaState);

42.}


本例中使用def_readwrite定義類成員,我們也可以用def_readonly把類成員定義成只讀。

LuaBind
還可以把C++類導出成支持gettersetter的屬性的Lua類:

1.  struct ResourceManager {

2.    ResourceManager() :

3.      m_ResourceCount(0) {}

4.   

5.    void loadResource(const string &sFilename) {

6.      ++m_ResourceCount;

7.    }

8.    size_t getResourceCount() const {

9.      return m_ResourceCount;

10.  }

11. 

12.  size_t m_ResourceCount;

13.};

14. 

15.int main() {

16.  lua_State *myLuaState = lua_open();

17.  luabind::open(myLuaState);

18. 

19.  // 導出類,在Lua中調用ResourceCount屬性會調用C++中的ResourceManager::getResourceCount

20.  // 屬性定義有點象C++Builder里的__property定義,呵呵

21.  luabind::module(myLuaState) [

22.    luabind::class_<ResourceManager>("ResourceManager")

23.      .def("loadResource", &ResourceManager::loadResource)

24.      .property("ResourceCount", &ResourceManager::getResourceCount)

25.  ];

26. 

27.  try {

28.    ResourceManager MyResourceManager;

29. 

30.    // MyResourceManager定義成Lua的全局變量

31.    luabind::globals(myLuaState)["MyResourceManager"] = &MyResourceManager;

32. 

33.    // 調用

34.    luaL_dostring(

35.      myLuaState,

36.      "MyResourceManager:loadResource(\"abc.res\") "

37.      "MyResourceManager:loadResource(\"xyz.res\") "

38.      " "

39.      "ResourceCount = MyResourceManager.ResourceCount "

40.    );

41. 

42.    // 讀出全局變量

43.    size_t ResourceCount = luabind::object_cast<size_t>(

44.      luabind::globals(myLuaState)["ResourceCount"]

45.    );

46.    cout << ResourceCount << endl;

47.  }

48.  catch(const std::exception &TheError) {

49.    cerr << TheError.what() << endl;

50.  }

51. 

52.  lua_close(myLuaState);

53.}

: Lua語法簡介

1.語法約定

    Lua語句用分號結尾,不過如果不寫分號,Lua也會自己判斷如何區分每條語句
   
:
        a=1 b=a*2 --
這樣寫沒有問題,但不太好看。
   
建議一行里有多個語句時用分號隔開

   
變量名、函數名之類的命名規則與C語言一樣:由字母,下劃線和數字組成,但第一個字符不能是數字。并且不能和Lua的保留字相同。
   
    Lua
是大小寫敏感的
   
   
使用兩個減號--作為單行注釋符,多行注釋使用--[[...--]]
   

2.類型

    Lua是動態類型語言,變量不要類型定義。Lua中有8個基本類型分別為:nilbooleannumberstringuserdatafunctionthreadtable
   
同一變量可以隨時改變它的類型,如:

1.  a = 10                  --number

2.  a = "hello"             --string

3.  a = false               --boolean

4.  a = {10,"hello",false}  --table

5.  a = print               --function

    使用type函數可以得到變量當前的類型,如print(type(a));
   
    nil        
所有沒有被賦值過的變量默認值為nil,給變量賦nil可以收回變量的空間。
    boolean    
取值falsetrue。但要注意Lua中所有的值都可以作為條件。在控制結構的條件中除了falsenil為假,其他值都為真。所以Lua認為0和空串都是真。(注意,和C不一樣哦)
    number     
表示實數,Lua中沒有整數。不用擔心實數引起的誤差,Luanumbers可以處理任何長整數。
    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        --
如果afalse,則返回a,否則返回b
        a or  b        --
如果atrue,則返回a,否則返回b
       
所以C中的三元運算符a?b:cLua中可以這樣寫:(a and b) or c
   
連接運算符:連續兩個小數點..,如:
        "hello" .. "world" 
結果是 "helloworld"
        0 .. 1             
結果是 "01",當在一個數字后面寫..時,必須加上空格以防止被解釋錯。
   
取長度操作符:一元操作 #
       
字符串的長度是它的字節數,table 的長度被定義成一個整數下標 n,它滿足 t[n] 不是 nil t[n+1] nil
   

4.基本語法

賦值

    a = a + 1
    Lua
里的賦值還可以同時給多個變量賦值。變量列表和值列表的各個元素用逗號分開,賦值語句右邊的值會依次賦給左邊的變量。如:
    a, b = 10, 2*x    --
相當于a=10; b=2*x
    x, y = y, x        --
交換xy
   
如果賦值符左右個數不同時,Lua會自動丟棄多余值或以nil補足

局部變量

    local i = 10
   
使用local聲明局部變量,局部變量只在所在的代碼塊內有效。
   
如果不聲明,默認為全局變量,這個變量在所有Lua環境中有效。
   
代碼塊是指一個控制結構內,一個函數體,或者一個chunk(變量被聲明的那個文件或者文本串),也可以直接使用do...end(相當于C中的{})。
條件

1.  if 條件 then

2.      then-part

3.  elseif 條件n then

4.      elseif-part

5.  ..                --->多個elseif

6.  else

7.      else-part

8.  end;

循環

    Lua中的循環有:while循環,repeat-until循環,for循環和for in循環。
   
循環中可以用break跳出,Lua語法要求breakreturn只能是代碼塊的最后一句(放心,正常的代碼都是滿足這個要求的,breakreuturn后面即使有代碼也是執行不到的,再說了,大不了自己加個do...end好了^_^
   
如:

1.  local i = 1

2.  while a[i] do

3.      if a[i] == v then break end

4.      i = i + 1

5.  end

while循環

1.  while condition do

2.      statements;

3.  end;

repeat-until循環:

1.  repeat

2.      statements;

3.  until conditions;

for循環

1.  for var=exp1,exp2,exp3 do

2.      loop-part

3.  end

    for將用exp3作為stepexp1(初始值)到exp2(終止值),執行loop-part。其中exp3可以省略,默認step=1

for in循環

1.  for 變量 in 集合 do

2.      loop-part

3.  end

    實際上,
    for var_1, ..., var_n in explist do block end
   
等價于

1.  do

2.      local _f, _s, _var = explist

3.      while true do

4.          local var_1, ... , var_n = _f(_s, _var)

5.          _var = var_1

6.          if _var == nil then break end

7.          block

8.      end

9.  end

    如:

1.  a = {"windows","macos","linux",n=3}

2.  for k,v in pairs(a) do print(k,v) end

5.函數

1.  function 函數名 (參數列表)

2.      statements-list;

3.  end;

    函數也可以一次返回多個值,如:

1.  function foo() return 'a','b','c'; end

2.  a,b,c = foo()


   
Lua中還可以直接定義匿名函數,如
    print((function() return 'a','b','c' end)())

 
 本文由用戶 lihz 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!