1. 程式人生 > >C/C++ 與lua混合使用入門講的比較好的文章

C/C++ 與lua混合使用入門講的比較好的文章

轉自 http://www.open-open.com/home/space-6246-do-blog-id-1426.html

Lua是一個嵌入式的指令碼語言,它不僅可以單獨使用還能與其它語言混合呼叫。
Lua與其它指令碼語言相比,其突出優勢在於:

  1. 可擴充套件性。Lua的擴充套件性非常卓越,以至於很多人把Lua用作搭建領域語言的工具(注:比如遊戲指令碼)。Lua被設計為易於擴充套件的,可以通過Lua程式碼或者 C程式碼擴充套件,Lua的很多功能都是通過外部庫來擴充套件的。Lua很容易與C/C++、java、fortran、Smalltalk、Ada,以及其他語言介面。
  2. 簡單。Lua本身簡單,小巧;內容少但功能強大,這使得Lua易於學習,很容易實現一些小的應用。他的完全釋出版(程式碼、手冊以及某些平臺的二進位制檔案)僅用一張軟盤就可以裝得下。
  3. 高效率。Lua有很高的執行效率,統計表明Lua是目前平均效率最高的指令碼語言。
  4. 與平臺無關。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。

要在C++中使用Lua非常簡單,不管是GCC,VC還是C++Builder, 最簡單的方法就是把

Lua原始碼中除lua.c,luac.c和print.c以外的所有c檔案與你的程式碼一起編譯連結(或加入到工程中)即可。
當然,為了方便維護,最好還是先把Lua編譯成庫檔案再加入工程。方法如下:

GCC

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

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

    如果你的環境不在這個列表中,你可以試試ansi或posix。

VC

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

標頭檔案

    因為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. #include <iostream>
  7. #include <string>
  8. using namespace std;

11.int main()

12.{

  1. lua_State *L = lua_open();    //初始化lua
  2. luaL_openlibs(L);    //載入所有lua標準庫
  3. string s;
  4. while(getline(cin,s))    //從cin中讀入一行到s
  5. {
  6. //載入s裡的lua程式碼後執行
  7. bool err = luaL_loadbuffer(L, s.c_str(), s.length(),
  8. "line") || lua_pcall(L, 0, 0, 0);
  9. if(err)
  10. {
  11. //如果錯誤,顯示
  12. cerr << lua_tostring(L, -1);
  13. //彈出錯誤資訊所在的最上層棧
  14. lua_pop(L, 1);
  15. }
  16. }
  17. lua_close(L);//關閉
  18. return 0;

33.}


    這已經是一個功能完備的互動方式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交換資料

  1. extern "C" {
  2. #include "lua.h"
  3. #include "lualib.h"
  4. #include "lauxlib.h"
  5. }
  6. #include <iostream>
  7. #include <string>
  8. using namespace std;

  11.int main()

    12.{

  1. //Lua示例程式碼
  2. char *szLua_code =
  3. "r = string.gsub(c_Str, c_Mode, c_Tag) --宿主給的變數 "
  4. "u = string.upper(r)";
  5. //Lua的字串模式
  6. char *szMode = "(%w+)%s*=%s*(%w+)";
  7. //要處理的字串
  8. char *szStr = "key1 = value1 key2 = value2";
  9. //目標字串模式
  10. char *szTag = "<%1>%2</%1>";
  11. lua_State *L = luaL_newstate();
  12. luaL_openlibs(L);
  13. //把一個數據送給Lua
  14. lua_pushstring(L, szMode);
  15. lua_setglobal(L, "c_Mode");
  16. lua_pushstring(L, szTag);
  17. lua_setglobal(L, "c_Tag");
  18. lua_pushstring(L, szStr);
  19. lua_setglobal(L, "c_Str");
  20. //執行
  21. bool err = luaL_loadbuffer(L, szLua_code, strlen(szLua_code),
  22. "demo") || lua_pcall(L, 0, 0, 0);
  23. if(err)
  24. {
  25. //如果錯誤,顯示
  26. cerr << lua_tostring(L, -1);
  27. //彈出棧頂的這個錯誤資訊
  28. lua_pop(L, 1);
  29. }
  30. else
  31. {
  32. //Lua執行後取得全域性變數的值
  33. lua_getglobal(L, "r");
  34. cout << "r = " << lua_tostring(L,-1) << endl;
  35. lua_pop(L, 1);
  36. lua_getglobal(L, "u");
  37. cout << "u = " << lua_tostring(L,-1) << endl;    
  38. lua_pop(L, 1);
  39. }
  40. lua_close(L);
  41. return 0;

58.}


    這段程式碼把字串中的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。
我們把上面的例子改成使用表的

  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. lua_State *L = luaL_newstate();
  16. luaL_openlibs(L);
  17. //把一個tabele送給Lua
  18. lua_newtable(L);    //新建一個table並壓入棧頂
  19. lua_pushstring(L, "Mode");// key
  20. lua_pushstring(L, szMode);// value
  21. //設定newtable[Mode]=szMode
  22. //由於上面兩次壓棧,現在table元素排在棧頂往下數第三的位置
  23. lua_settable(L, -3);
  24. //lua_settable會自己彈出上面壓入的key和value
  25. lua_pushstring(L, "Tag");// key
  26. lua_pushstring(L, szTag);// value
  27. lua_settable(L, -3);    //設定newtable[Tag]=szTag
  28. lua_pushstring(L, "Str");// key
  29. lua_pushstring(L, szStr);// value
  30. lua_settable(L, -3);    //設定newtable[Str]=szStr
  31. lua_setglobal(L,"c"); //將棧頂元素(newtable)置為Lua中的全域性變數c
  32. //執行
  33. bool err = luaL_loadbuffer(L, szLua_code, strlen(szLua_code),
  34. "demo") || lua_pcall(L, 0, 0, 0);
  35. if(err)
  36. {
  37. //如果錯誤,顯示
  38. cerr << lua_tostring(L, -1);
  39. //彈出棧頂的這個錯誤資訊
  40. lua_pop(L, 1);
  41. }
  42. else
  43. {
  44. //Lua執行後取得全域性變數的值
  45. lua_getglobal(L, "x");
  46. //這個x應該是個table
  47. if(lua_istable(L,-1))
  48. {
  49. //取得x.u,即x["u"]
  50. lua_pushstring(L,"u");    //key
  51. //由於這次壓棧,x處於棧頂第二位置
  52. lua_gettable(L,-2);
  53. //lua_gettable會彈出上面壓入的key,然後把對應的value壓入
  54. //取得資料,然後從棧中彈出這個value
  55. cout << "x.u = " << lua_tostring(L,-1) << endl;
  56. lua_pop(L, 1);
  57. //取得x[1]和x[2]
  58. for(int i=1; i<=2; i++)
  59. {
  60. //除了key是數字外,與上面的沒什麼區別
  61. lua_pushnumber(L,i);
  62. lua_gettable(L,-2);
  63. cout << "x[" << i <<"] = " << lua_tostring(L,-1) << endl;
  64. lua_pop(L, 1);
  65. }
  66. }
  67. //彈出棧頂的x
  68. lua_pop(L, 1);
  69. }
  70. lua_close(L);
  71. return 0;

81.}

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

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來取得函式,剩下的問題只是怎樣執行它(函式元素)的問題了。

  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. lua_State *L = luaL_newstate();
  18. luaL_openlibs(L);
  19. //執行
  20. bool err = luaL_loadbuffer(L, szLua_code, strlen(szLua_code),
  21. "demo") || lua_pcall(L, 0, 0, 0);
  22. if(err)
  23. {
  24. cerr << lua_tostring(L, -1);
  25. lua_pop(L, 1);
  26. }
  27. else
  28. {
  29. //Lua執行後取得全域性變數的值
  30. lua_getglobal(L, "gsub");
  31. if(lua_isfunction(L,-1))    //確認一下是個函式
  32. {
  33. //依次放入三個引數
  34. lua_pushstring(L,szStr);
  35. lua_pushstring(L,szMode);
  36. lua_pushstring(L,szTag);
  37. //呼叫,我們有3個引數,要得到2個結果
  38. //你可能注意到gsub函式返回了3個,不過我們只要2個,這沒有問題
  39. //沒有使用錯誤處理回撥,所以lua_pcall最後一個引數是0
  40. if(0 != lua_pcall(L, 3, 2, 0))
  41. {
  42. //如果錯誤,顯示
  43. cerr << lua_tostring(L, -1);
  44. lua_pop(L, 1);                
  45. }
  46. else
  47. {
  48. //正確,得到兩個結果,注意在棧裡的順序
  49. cout << "a = " << lua_tostring(L, -2) << endl;
  50. cout << "b = " << lua_tostring(L, -1) << endl;
  51. //彈出這兩個結果
  52. lua_pop(L, 2);
  53. }
  54. }
  55. else
  56. {
  57. lua_pop(L,1);
  58. }
  59. }
  60. lua_close(L);
  61. 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. //C函式,做複數計算,輸入實部,虛部。輸出絕對值和角度
  4. int calcComplex(lua_State *L)
  5. {
  6.     //從棧中讀入實部,虛部
  7.     double r = luaL_checknumber(L,1);
  8.     double i = luaL_checknumber(L,2);
  9. complex<double> c(r,i);
  10. //存入絕對值
  11. lua_pushnumber(L,abs(c));
  12. //存入角度
  13. lua_pushnumber(L,arg(c)*180.0/3.14159);
  14. return 2;//兩個結果

16.}

18.int main()

19.{

  1. char *szLua_code =
  2. "v,a = CalcComplex(3,4) "
  3. "print(v,a)";
  4. lua_State *L = luaL_newstate();
  5. luaL_openlibs(L);
  6. //放入C函式
  7. lua_pushcfunction(L, calcComplex);
  8. lua_setglobal(L, "CalcComplex");
  9. //執行
  10. bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
  11. if(err)
  12. {
  13. cerr << lua_tostring(L, -1);
  14. lua_pop(L, 1);
  15. }
  16. lua_close(L);
  17. return 0;

41.}

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

閉包(closure)

    在編寫用於Lua的C函式時,我們可能需要一些類似於面向物件的能力,比如我們想在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.{

  1. //計數器初值(即UpValue)
  2. lua_pushnumber(L,0);
  3. //放入計算函式,告訴它與這個函式相關聯的資料個數
  4. lua_pushcclosure(L, count, 1);
  5. return 1;//一個結果,即函式體

22.}

24.int main()

25.{

  1. char *szLua_code =
  2. "c1 = NewCount() "
  3. "c2 = NewCount() "
  4. "for i=1,5 do print(c1()) end "
  5. "for i=1,5 do print(c2()) end";
  6. lua_State *L = luaL_newstate();
  7. luaL_openlibs(L);
  8. //放入C函式
  9. lua_register(L,"NewCount",newCount);
  10. //執行
  11. bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
  12. if(err)
  13. {
  14. cerr << lua_tostring(L, -1);
  15. lua_pop(L, 1);
  16. }
  17. lua_close(L);
  18. 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_GLOBALSINDEX和LUA_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.     lua_State *L = luaL_newstate();
  8.     luaL_openlibs(L);
  9. //執行
  10. bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
  11. if(err)
  12. {
  13. cerr << lua_tostring(L, -1);
  14. lua_pop(L, 1);
  15. }
  16. else
  17. {
  18. //遍歷LUA_GLOBALSINDEX所在的table得到
  19. lua_pushnil(L);
  20. while(0 != lua_next(L,LUA_GLOBALSINDEX))
  21. {
  22. // 'key' (在索引 -2 處) 和 'value' (在索引 -1 處)
  23. /*
  24. 在遍歷一張表的時候,不要直接對 key 呼叫 lua_tolstring ,
  25. 除非你知道這個 key 一定是一個字串。
  26. 呼叫 lua_tolstring 有可能改變給定索引位置的值;
  27. 這會對下一次呼叫 lua_next 造成影響。
  28. 所以複製一個key到棧頂先
  29. */
  30. lua_pushvalue(L, -2);
  31. printf("%s - %s ",
  32. lua_tostring(L, -1),    //key,剛才複製的
  33. lua_typename(L, lua_type(L,-2))); //value,現在排在-2的位置了
  34. // 移除 'value' 和複製的key;保留源 'key' 做下一次疊代
  35. lua_pop(L, 2);
  36. }
  37. }
  38. lua_close(L);
  39. return 0;

42.}


    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。

  1. extern "C" {
  2. #include "lua.h"
  3. #include "lualib.h"
  4. #include "lauxlib.h"
  5. }
  6. #include <iostream>
  7. #include <string>
  8. #include <windows.h>

10.using namespace std;

12.//函式庫示例,Windows下查詢檔案功能

13.//輸入:string路徑名

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

15.int findfirst( lua_State *L )

16.{

  1. WIN32_FIND_DATAA FindFileData;
  2. HANDLE hFind = ::FindFirstFileA(luaL_checkstring(L,1), &FindFileData);
  3. if(INVALID_HANDLE_VALUE == hFind)
  4. lua_pushnil(L);
  5. else
  6. lua_pushlightuserdata(L, hFind);
  7. lua_pushstring(L, FindFileData.cFileName);
  8. return 2;

28.}

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

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

32.int findnext( lua_State *L )

33.{

  1. WIN32_FIND_DATAA FindFileData;
  2. if(::FindNextFileA(lua_touserdata(L,1),&FindFileData))
  3. lua_pushstring(L, FindFileData.cFileName);
  4. else
  5. lua_pushnil(L);
  6. return 1;

40.}

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

43.//沒有輸出

44.int findclose( lua_State *L )

45.{

  1. ::FindClose(lua_touserdata(L,1));
  2. return 0;

48.}

50.//註冊函式庫

51.static const struct luaL_reg lrFiles [] = {

  1. {"FindFirst", findfirst},
  2. {"FindNext", findnext},
  3. {"FindClose", findclose},
  4. {NULL, NULL}    /* sentinel */

56.};

57.int luaopen_Files (lua_State *L) {

  1. luaL_register(L, "Files", lrFiles);
  2. return 1;

60.}

62.int main()

63.{

  1. char* szLua_code=
  2. "hFind,sFile = Files.FindFirst('c:\\\\*.*'); "
  3. "if hFind then "
  4. "    repeat "
  5. "        print(sFile) "
  6. "        sFile = Files.FindNext(hFind) "
  7. "    until sFile==nil; "
  8. "    Files.FindClose(hFind) "
  9. "end";
  10. lua_State *L = luaL_newstate();
  11. luaL_openlibs(L);
  12. luaopen_Files(L);
  13. bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
  14. if(err)
  15. {
  16. cerr << lua_tostring(L, -1);
  17. lua_pop(L, 1);
  18. }
  19. lua_close(L);
  20. 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. BOOL APIENTRY DllMain( HMODULE hModule,
  8.                        DWORD  ul_reason_for_call,
  9. LPVOID lpReserved
  10. )

12.{

  1. return TRUE;

14.}

16.//函式庫示例,Windows下查詢檔案功能

17.//輸入:string路徑名

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

19.int findfirst( lua_State *L )

20.{

  1. WIN32_FIND_DATAA FindFileData;
  2. HANDLE hFind = ::FindFirstFileA(luaL_checkstring(L,1), &FindFileData);
  3. if(INVALID_HANDLE_VALUE == hFind)
  4. lua_pushnil(L);
  5. else
  6. lua_pushlightuserdata(L, hFind);
  7. lua_pushstring(L, FindFileData.cFileName);
  8. return 2;

32.}

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

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

36.int findnext( lua_State *L )

37.{

  1. WIN32_FIND_DATAA FindFileData;
  2. if(::FindNextFileA(lua_touserdata(L,1),&FindFileData))
  3. lua_pushstring(L, FindFileData.cFileName);
  4. else
  5. lua_pushnil(L);
  6. return 1;

44.}

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

47.//沒有輸出

48.int findclose( lua_State *L )

49.{

  1. ::FindClose(lua_touserdata(L,1));
  2. return 0;

52.}

54.//註冊函式庫

55.static const struct luaL_reg lrFiles [] = {

  1. {"FindFirst", findfirst},
  2. {"FindNext", findnext},
  3. {"FindClose", findclose},
  4. {NULL, NULL}    /* sentinel */

60.};

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

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

  1. luaL_register(L, "Files", lrFiles);
  2. 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. #include <iostream>
  7. #include <string>
  8. #include <windows.h>

10.using namespace std;

12.int main()

13.{

  1. char* szLua_code=
  2. "fileslib = package.loadlib('fileslib.dll', 'luaopen_Files') "