簡單運行Lua代碼

引用:http://blog.csdn.net/hamenny/article/details/4420522

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.cluac.cprint.c以外的所有c文件與你的代碼一起編譯鏈接(或加入到工程中)即可。
當然,爲了方便維護,最好還是先把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頭文件:
  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
    輸出從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.  
  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_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.  
  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會自己彈出上面壓入的key和value
  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_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.  
  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 作爲出錯信息返回在堆棧上。 

例四,在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_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. {
  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_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.  
  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的域來保存。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.  
  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++Builder的 VCL庫,所以下面的代碼要在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);
    檢查在棧中指定位置的對象是否爲帶有給定名字的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後,我們還得到了額外的好處:可以區分不同的userdata以保證類型安全,我們把所以的lua_touserdata改成了luaL_checkudata。
    關於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的頭文件路徑。

頭文件:

  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函數那是最簡單不過的事情了,用LuaBind的call_function()模板函數就可以了: 
  1. int main(
  2.   // 建立新的Lua環境
  3.   lua_State *myLuaState = luaL_newstate();
  4.  
  5.   // 讓LuaBind“認識”這個Lua環境
  6.   luabind::open(myLuaState);
  7.  
  8.   // 定義一個叫add的Lua函數
  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::def把print_hello函數提供給Lua環境,第一個參數是Lua中使用的函數名。
    如果要定義多個函數,可以使用逗號分隔。

在Lua代碼中使用C++類

    如果我們直接使用Lua C API向Lua腳本註冊一個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++類導出成支持getter和setter的屬性的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個基本類型分別爲:nil、boolean、number、string、userdata、function、thread和table。
    同一變量可以隨時改變它的類型,如:
  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     取值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 + 1
    Lua裏的賦值還可以同時給多個變量賦值。變量列表和值列表的各個元素用逗號分開,賦值語句右邊的值會依次賦給左邊的變量。如:
    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中的{})。
條件
  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語法要求break和return只能是代碼塊的最後一句(放心,正常的代碼都是滿足這個要求的,break和 reuturn後面即使有代碼也是執行不到的,再說了,大不了自己加個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作爲step從exp1(初始值)到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)())
        
更詳細信息請參考<LUA>/doc/manual.html


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章