Lua腳本在C++下的舞步(一)(入門指引)

現在,越來越多的C++服務器和客戶端融入了腳本的支持,尤其在網遊領域,腳本語言已經滲透到了方方面面,比如你可以在你的客戶端增加一個腳本,這個腳本將會幫你在界面上顯示新的數據,亦或幫你完成某些任務,亦或幫你查看別的玩家或者NPC的狀態。。。如此等等。

但是我覺得,其實腳本語言與C++的結合,遠遠比你在遊戲中看到的特效要來的迅猛。它可以運用到方方面面的領域,比如你最常見的應用領域。比如,你可以用文本編輯器,寫一個腳本語言,然後用你的程序加載一下,就會產生出很絢麗的界面。亦或一兩句文本語言,就會讓你的程序發送數據給服務器,是不是很酷呢?
本來我想,寫一篇關於主流腳本語言Lua和Python的文章,但是感覺這樣過於乏味,於是分開來一一介紹,相信對C++瞭解的你,看過我的文章後會對腳本語言這種東西產生濃厚的興趣,我想起以前聽的一個故事,當年Java的創造者講課的時候,一開始先拿一個簡單的不能簡單的小例子,不斷的擴展,最後成爲一個複雜而完美的程序。今天我也就這樣實驗一下吧,呵呵。

當然,我本人不敢說對腳本語言瞭如指掌,只能說略微掌握一些,用過幾年,偏頗之處請大家指正。 
下面,開始吧,先說LUA!(本文面向初學者)

Lua語言(http://www.lua.org/),想必不少程序員都聽過,據我所知,由於《魔獸世界》裏面對它的加載,它一下子變成了很多遊戲開發者競相研究的對象,至於這個巴西創造者麼,我不過多介紹,大家有興趣可以谷歌一下。其實網上有很多關於lua的教材和例子,說真的,對於當年的我而言,幾乎看不懂,當時很鬱悶,感覺Lua複雜的要命,有些懼怕,後來沉下心來一點點研究,覺得其實還是蠻簡潔的。只是網上的資料或許偏向於某些功能,導致了邏輯和代碼的複雜。後來總結,其實學習一種腳本語言,完全可以抱着放鬆的心態一點點的研究,反而效果會更好。

在講代碼之前,我要說Lua的一些特點,這些特點有利於你在複雜的代碼調用中,清晰的掌握中間的來龍去脈。實際上,你能常常用到的lua的API,不過超過10個,再複雜的邏輯。基本上也是這麼多API組成的。至於它們是什麼,下面的文章會介紹。另外一個重要之重要的概念,就是棧。Lua與別的語言交互以及交換數據,是通過棧完成的。其實簡單的解釋一下,你可以把棧想象成一個箱子,你要給他數據,就要按順序一個個的把數據放進去,當然,Lua執行完畢,可能會有結果返回給你,那麼Lua還會利用你的箱子,一個個的繼續放下去。而你取出返回數據呢,要從箱子頂上取出,如果你想要獲得你的輸入參數呢?那也很簡單,按照頂上返回數據的個數,再按順序一個個的取出,就行了。不過這裏提醒大家,關於棧的位置,永遠是相對的,比如-1代表的是當前棧頂,-2代表的是當前棧頂下一個數據的位置。棧是數據交換的地方,一定要有一些棧的概念。

好了,基礎的lua語法不在這裏講,百度一下有很多。 
先去
http://www.lua.org/ 去下載一個最新的Lua代碼(現在穩定版是lua-5.1.4)。它的代碼是用C寫的,所以很容易兼容很多平臺。
在linux下,目錄src下就有專門的Makefile。很簡單,啥都不用做,指定一下位置編譯即可。 
在windows下,以VS2005爲例,建立一個空的靜態庫工程(最好不使用預編譯頭,把預編譯頭的選項勾去掉),然後把src下的所有文件(除了Makefile)一股腦拷到工程中去。然後將這些文件添加到你的工程中,編譯,會生成一個*.llib(*是你起的lua庫名),行了,建立一個目錄lib,把它拷過去,然後再建立一個include的文件夾,把你工程目錄下的lua.h,lualib.h,lauxlib.h,拷貝過去。行了,拿着這兩個文件夾,你就可以在你的工程裏使用lua了。
行了,材料齊了,我們來看看怎麼寫一個簡單的lua程序吧。

建立一個文件,起名Sample.lua 
裏面添加這樣的代碼。 
function func_Add(x, y) 
   return x+y; 
end

這是一個標準的lua語法,一個函數,實現簡單的a+b操作,並返回操作結果。 
保存退出。 
多一句嘴,在Lua裏面,是可以支持多數據返回的。 
比如你這麼寫: 
function func_Add(x, y) 
   return x+y, x-y; 
end

意思是返回第一個參數是相加的結果,第二個是相減的結果,也是可以的。在lua裏面沒有類型的概念。當然,在C++接受這樣的返回值的時候,也很簡單,請往下看。
好了,材料齊備了,咱們來看看C++程序怎麼調用它。 
首先,建立一個類,負責加載這個lua文件,並執行函數操作,我們姑且叫做CLuaFn 
要加載這個lua文件,按照正常的思路,我們應該先加載,然後再調用不同的函數。恩,對了,咱們就這麼做。

extern “C” 

        #include “lua.h” 
        #include “lualib.h” 
        #include “lauxlib.h” 
};

class CLuaFn 

public: 
        CLuaFn(void); 
        ~CLuaFn(void);

        void Init();            //初始化Lua對象指針參數 
        void Close();         //關閉Lua對象指針

        bool LoadLuaFile(const char* pFileName);                              //加載指定的Lua文件
        bool CallFileFn(const char* pFunctionName, int nParam1, int nParam2);        //執行指定Lua文件中的函數

private: 
        lua_State* m_pState;   //這個是Lua的State對象指針,你可以一個lua文件對應一個。 
};

恩,頭文件就這麼多,看看,一點也不復雜吧,看了cpp我想你會更高興,因爲代碼一樣很少。我一個個函數給你們介紹。

void CLuaFn::Init() 

        if(NULL == m_pState) 
        { 
                m_pState = lua_open(); 
                luaL_openlibs(m_pState); 
        } 
}

初始化函數,標準代碼,沒啥好說的,lua_open()是返回給你一個lua對象指針,luaL_openlibs()是一個好東西,在lua4,初始化要做一大堆的代碼,比如加載lua的string庫,io庫,math庫等等等等,代碼洋洋灑灑一大堆,其實都是不必要的,因爲這些庫你基本都需要用到,除了練習你的打字能力別的意義不大,因爲代碼寫法都是固定的。於是在5以後,Lua的創造者修改了很多,這就是其一,一句話幫你加載了所有你可能用到的Lua基本庫。

void CLuaFn::Close() 

        if(NULL != m_pState) 
        { 
                lua_close(m_pState); 
                m_pState = NULL; 
        } 
}

顧名思義,我用完了,關閉我的Lua對象並釋放資源。呵呵,標準寫法,沒啥好說的。

bool CLuaFn:: LoadLuaFile(const char* pFileName) 

        int nRet = 0; 
        if(NULL == m_pState) 
        { 
                printf(“[CLuaFn:: LoadLuaFile]m_pState is NULL./n”); 
                return false; 
        }

        nRet = luaL_dofile(m_pState, pFileName); 
        if (nRet != 0) 
        { 
                printf(“[CLuaFn:: LoadLuaFile]luaL_loadfile(%s) is file(%d)(%s)./n”, pFileName, nRet, lua_tostring(m_pState, -1));
                return false; 
        }

        return true; 
}

呵呵,這個有點意思,加載一個Lua文件。 
這裏我要詳細的說一下,因爲Lua是腳本語言,加載lua文件本身的時候纔會編譯。 
所以,推薦大家在加載文件的時候儘量放在程序的初始化中,因爲當你執行luaL_dofile()函數的時候,Lua會啓用語法分析器,去分析你的腳本語法是否符合Lua規則,如果你胡亂的傳一個文件過去,Lua就會告訴你文件語法錯誤,無法加載。如果你的Lua腳本很大,函數很多,語法分析器會比較耗時,所以,加載的時候,儘量放在合適的地方,而且,對於一個Lua文件而言,反覆加載luaL_dofile()除了會使你的CPU變熱沒有任何意義。

或許你對printf(“[CLuaFn:: LoadLuaFile]luaL_loadfile(%s) is file(%d)(%s)./n”, pFileName, nRet, lua_tostring(m_pState, -1));這句話很感興趣,這個在幹什麼?這裏我先說lua_tostring(m_pState, -1)這是在幹什麼,還記得我說的Lua是基於棧傳輸數據的麼?那麼,如果報錯,我怎麼知道錯誤是什麼?luaL_dofile標準返回一個int,我總不能到lua.h裏面遍歷這個nRet 是啥意思吧,恩,Lua創造者早就爲你想好了,只不過你需要稍微動一下你的腦筋。Lua的創造者在語法分析器分析你的語法的時候,發現錯誤,會有一段文字告訴你是什麼錯誤,它會把這個字符串放在棧頂。那麼,怎麼取得棧頂的字符串呢?lua_tostring(m_pState, -1)就可以,-1代表的是當前棧的位置是相對棧頂。當然,你也可以看看棧裏面還有一些什麼其他古怪的數據,你可以用1,2,3(這些是絕對位置,而-1是相對位置)去嘗試,呵呵。不過,相信你得到的也很難看懂,因爲一個Lua對象執行的時候,會用很多次棧進行數據交換,而你看到的,有可能是交換中的數據。那麼,話說回來,這句話的意思就是”[CLuaFn:: LoadLuaFile]luaL_loadfile(文件名) is file(錯誤編號)(錯誤具體描述文字)./n”

bool CLuaFn::CallFileFn(const char* pFunctionName, int nParam1, int nParam2)

        int nRet = 0; 
        if(NULL == m_pState) 
        { 
                printf(“[CLuaFn::CallFileFn]m_pState is NULL./n”); 
                return false; 
        }

        lua_getglobal(m_pState, pFunctionName);

        lua_pushnumber(m_pState, nParam1); 
        lua_pushnumber(m_pState, nParam2);

        nRet = lua_pcall(m_pState, 2, 1, 0); 
        if (nRet != 0) 
        { 
                printf(“[CLuaFn::CallFileFn]call function(%s) error(%d)./n”, pFunctionName, nRet);
                return false; 
        }

        if (lua_isnumber(m_pState, -1) == 1) 
        { 
                int nSum = lua_tonumber(m_pState, -1); 
                printf(“[CLuaFn::CallFileFn]Sum = %d./n”, nSum); 
        }

        return true; 
}

這個函數是,傳入函數名稱和參數,去你的Lua文件中去執行。 
lua_getglobal(m_pState, pFunctionName); 
這個函數是驗證你的Lua函數是否在你當前加載的Lua文件中,並把指針指向這個函數位置。

lua_pushnumber(m_pState, nParam1);   <—對應你的x參數 
lua_pushnumber(m_pState, nParam2);   <—對應你的y參數

這就是著名的壓棧操作了,把你的參數壓入Lua的數據棧。供Lua語法器去獲得你的數據。 
lua_pushnumber()是一個壓入數字,lua_pushstring()是壓入一個字符串。。。

那麼你會問,如果我有一個自己的類型,一個類指針或者別的什麼,我怎麼壓入?彆着急,方法當然是有的,呵呵,不過你先看看如果簡單的如何做,在下幾講中,我會告訴你更強大的Lua壓棧藝術。
這裏需要注意的是,壓棧的順序,對,簡單說,就是從左到右的參數,左邊的先進棧,右邊的最後進棧。

nRet = lua_pcall(m_pState, 2, 1, 0); 
這句話的意思是,執行這個函數,2是輸入參數的個數,1是輸出參數的個數。當然,如果你把Lua函數改成 
return x+y, x-y; 
代碼需要改成nRet = lua_pcall(m_pState, 2, 2, 0); 
明白了吧,呵呵,很簡單吧。 
當然,如果函數執行失敗,會觸發nRet,我這裏偷了個懶,如果你想得到爲什麼錯了?可以用lua_tostring(m_pState, -1)去棧頂找,明白?是不是有點感覺了?

lua_isnumber(m_pState, -1) 
這句話是判定棧頂的元素是不是數字。因爲如果執行成功,棧頂就應該是你的數據返回值。 
int nSum = lua_tonumber(m_pState, -1); 
printf(“[CLuaFn::CallFileFn]Sum = %d./n”, nSum); 
這個nSum就是返回的結果。 
當然,你會問,如果 return x+y, x-y;我該怎麼辦? 
int nSum = lua_tonumber(m_pState, -1); 
int nSub = lua_tonumber(m_pState, -2); 
搞定,看見沒。按照壓棧順序。呵呵,是不是又有感覺了,對,棧就是數據交互的核心。對Lua的理解程度和運用技巧,其實就是對棧的靈活運用和操作。 
好了。你的第一個Lua程序大功告成!竟然不是Hello world,呵呵。 
好了,我們看看Main函數怎麼寫吧,相信大家都會寫。

#include “LuaFn.h”

int _tmain(int argc, _TCHAR* argv[]) 

        CLuaFn LuaFn;

        //LuaFn.InitClass();

        LuaFn.LoadLuaFile(“Sample.lua”); 
        LuaFn.CallFileFn(“func_Add”, 11, 12); 
        getchar();

        return 0; 
}

行了,Build一下,看看,是不是你要的結果?如果是,賀喜你,你已經邁出了Lua的第一步。 
洋洋灑灑寫了一個小時,喝口水吧,呵呵,下一講,我將強化這個LuaFn類,讓它給我做更多的事情。呵呵,最後,我會讓你打到,用Lua文件直接畫出一個Windows窗體來。並在上面畫出各種按鈕,列表,以及複選框。是不是感覺很酷?用文本去創造一個程序?很激動吧,恩,確實,Lua能給你做到。只要你有耐心看下去。。。

 

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