Lua使用心得(1)

轉載地址:http://blog.csdn.net/dylgsy/article/details/4100417

這幾天研究了一下lua,主要關注的是lua和vc之間的整合,把代碼都寫好放在VC宿主程序裏,然後在lua裏調用宿主程序的這些代碼(或者叫接口、組件,隨便你怎麼叫),希望能用腳本來控制主程序的行爲。這實際上也是一種把業務分離,用腳本控制的架構,可能有些人把這種腳本叫做業務引擎,工作流等。

 

爲什麼選擇lua?

因爲它是一個能和C/C++結合得很緊的腳本語言,而我們的程序是用VC++ 寫的;另外一點是因爲它的名氣,連WOW都用lua來提供API讓玩家修改其遊戲行爲,那我是找不到什麼理由拒絕它了。

 

Lua是什麼?在哪裏獲取LUA?

詳細的不說了,在網上一搜大把,只說一下它的官網吧:www.lua.org,在這裏可以查到lua的應用,lua發佈的版本,我用的是5.1.4,下載的是源代碼的版本。

 

LUA和VC MFC的整合?

  • 1、 包含LUA:要使用LUA,當然要先把它包含進我們的工程裏,可以有lib/dll方式、也可以用靜態lib方式,當然也可以把整個lua的代碼放進我們的工程,然後編譯,因爲lua只有幾百K,很小。。。包含整個代碼的方法我說一下:
  • a) 在VC MFC新建一個工程(例如Dialog base工程)
  • b) 去到工程裏的文件tab頁,新建一個文件夾,然後把所有lua裏的.c、.h文件包含進來,注意有幾個不用包含,lua.c、wmain.c、luac.c,包含進來之後,選中這個文件夾下面的所有.c文件,然後右鍵選setting,選擇Not using precompiler file,具體自己找找哈。這步做完就馬上編譯一下,應該是沒問題的了!
  • c) 還有動態庫和靜態lib兩種方式把lua包含進工程裏的,自己可以嘗試一下。
  • 2、 在某個地方(我是在MFCLua1Dlg.cpp文件開始處)包含lua的頭文件,並聲明一個lua_state指針,如下:

extern "C"

{

#include "lua.h"

#include "lualib.h"

#include "lauxlib.h"

}

lua_State *lua;

 

在某個適當的地方(我是在OnInitDialog裏)調用下面一段代碼,這段代碼的作用是打開一些必要的庫:

       lua = lua_open (); 

       if(lua)

       {

              luaopen_base (lua);

              luaopen_table (lua);

              luaopen_string (lua);

              luaopen_math (lua);

              luaopen_debug (lua);

              //luaopen_io (lua);

       }

 

用完lua的時候,調用下面一句來關閉lua庫:

lua_close (lua);

             

好了,到現在爲止,lua已經完全變成我們程序的一部分了,試着編譯一下,看看能不能順利通過。。。

 

LUA和MFC的交互?

 

Lua變成我們程序的一部分之後,我們還要使用它,要記住我們的目標是用腳本程序控制我們宿主程序的執行流程,那我們就要完成兩步,一是用mfc程序調用lua的函數,二是用lua調用mfc的函數,下面的內容對於初學者可能會開始有點難理解了,請打醒十二分精神,我會盡量簡單的說。。。

 

1、 mfc調用lua的函數,這裏用到一個StackDump的函數,是關於主程序和lua的交互棧的問題,下面會對交互棧的問題專門說明。

 

首先我們用記事本建立一個test.lua,內容是一個相加函數:

 

function add ( x, y )

return x + y;

end

 

然後再VC裏調用它,如下的一段代碼,看這段代碼的時候,先把StackDump函數忽略,只需要知道它是一個輸出lua和vc交互棧內容的函數,對了,你可以新建一個button的click函數,然後把這段代碼放進去:

 

StackDump(lua);   

luaL_dofile(lua, "test.lua");     // 解釋分析lua文件

StackDump(lua);

lua_getglobal(lua, "add");       // 取到一個全局標號add,取的同時會把add函數壓棧

StackDump(lua);   

lua_pushnumber(lua, 1);        // 把第一個參數壓入棧裏

StackDump(lua);

lua_pushnumber(lua, 2);        // 第二個參數壓棧

StackDump(lua);

//lua_call(lua, 2, 1);

if(lua_pcall(lua, 2, 1, 0) != 0)        // 執行add函數

{

        AfxMessageBox("lua_pcall error!");            

return;

}

StackDump(lua);

int d = (int)lua_tonumber(lua, -1);        // 函數執行完了,執行結果被壓棧,所以取得最頂端的一個數就是結果值,-1就是指取棧頂的值

CString str;

str.Format("%d", d);

AfxMessageBox(str);

StackDump(lua);

lua_pop(lua, 1);      // 把值從棧裏清除,pop(彈出)一個值

StackDump(lua);

 

好好分析一下這段代碼,我們大概知道調用lua函數的一個過程是:dofile--〉函數名壓棧--〉參數依序壓棧--〉lua_pcall執行(執行結果壓棧)--〉取出執行結果(如果有多個,就從棧裏取出多個。。。),這樣我們就能很輕鬆的調用到lua裏的函數,其實就是要知道棧裏發生了什麼。。。

 

2、 lua調用MFC函數,比如我們想在lua裏調用一個Msg函數,能彈出一個窗口來顯示我們想顯示的字串,然後返回值是1個"MsgOK!"字串。

 

lua文件是這樣的,第一句是調用Msg函數,第二句是測試返回的字串是不是"MsgOK!":

c = Msg ("123");

Msg(c);

 

MFC程序裏是這樣的:

首先寫一個將要被導出的函數,很多文章叫它爲粘合函數(glue function):

static int Msg(lua_State* L)

{

const char *s1 = luaL_checkstring(L, 1);     // 測試第一個參數是否爲字串形式,並取得這個字串

StackDump(L);

MessageBox(NULL, s1, "caption", MB_OK);

lua_pop(lua, 1);      // 清除棧裏的這個字串

StackDump(L);

lua_pushlstring(L, "MsgOK!", 6);  // 把返回值壓進棧裏

 

// 這個返回是指返回值的個數

return 1;

}

 

       然後就導出這個函數,如下:

       lua_pushcfunction(lua, Msg);

       lua_setglobal(lua, "Msg");

 

       接着就執行剛纔的lua文件就行了,記得執行之前要先lua_open () 哦:

       luaL_dofile(lua, "test.lua");

 

       運行的結果就是連續跳出兩個messagebox,第一個是123,第二個是"MsgOK!",說明我們返回的字串被lua接收到了,lua的第二行我們沒有接收它的返回值,則這個返回值會自動被拋棄了。

       如果需要多返回值,則我們要把下面一句:

lua_pushlstring(L, "MsgOK!", 6);  // 把返回值壓進棧裏

// 這個返回是指返回值的個數

return 1;

 

改爲:

lua_pushlstring(L, "MsgOK!", 6);  // 把返回值壓進棧裏

lua_pushlstring(L, "haha!", 5);      // 把返回值壓進棧裏

// 這個返回是指返回值的個數

return 2;

 

這樣我們在lua文件裏就可以像下面一樣取得兩個返回值了:

c,d = Msg("123");

那c和d就分別是"MsgOK!"和"haha!"兩個字串了。 這種自動機制用起來還是比較方便的。

 

3、交互棧

 

上面兩個調用其實都是對lua棧的實用,那我們就要好好理解一個概念,lua和vc的交互棧(棧是什麼?請參考數據結構的書哈。。。)lua和vc就是通過這個棧來實現交互的,這個棧的訪問函數有lua_gettop,lua_settop,lua_tostring,lua_toXXX等等的函數,我們要清楚當一個函數調用發生的時候,棧裏是發生了什麼。上面我用了一個StackDump函數,當我們調用的時候,能很清楚的看到棧裏發生了什麼。

       首先我們要知道從棧頂往下數就是-1、-2,從棧底往上數就是1、2。

如果使用lua_gettop(L, 1),就是取得棧底第一個元素。lua_gettop(L, -1)就是取得棧頂的第一個元素。lua_pop() (L, 1)就是把棧頂的一個元素彈出來,lua_pop()(L, 2)就是把棧頂的兩個元素彈出。

 

好了,寫了一通,最後是這個StackDump函數的實現:

int StackDump(lua_State* L)

{

       int nTop = lua_gettop(L); //得到棧的元素個數。棧頂的位置。

       OutputDebugString("The Length of stack is %d/n", nTop); //輸出棧頂位置

       for (int i = 1; i <= nTop; ++i)

       {

              int t = lua_type(L, i);

              OutputDebugString("%s:", lua_typename(L, t)); //這裏的typename是把類型的枚舉變成字符串,是類型名。不是棧中的位置。

              switch(t)

              {

              case LUA_TNUMBER:

                     OutputDebugString("%f", lua_tonumber(L, i));

                     break;

              case LUA_TSTRING:

                     OutputDebugString("%s", lua_tostring(L, i));

                     break;

              case LUA_TTABLE:

                     //OutputDebugString("%s/n", lua_tostring(L,i));

                     break;

              case LUA_TFUNCTION:

                     //OutputDebugString("%s/n", lua_tostring(L,i));

                     break;

              case LUA_TNIL:

                     OutputDebugString("Is NULL");

                     break;

              case LUA_TBOOLEAN:

                     OutputDebugString("%s", lua_toboolean(L, i) ? "true" : "false");

                     break;

              default:

                     break;

              }

              OutputDebugString("/n");

       }

       return 0;

}

 

本篇文章主要講了lua和VC的整合、把LUA源代碼和VC工程一起編譯,VC調用LUA的代碼,LUA調用VC的代碼,返回值以及多個返回值、交互棧、輸出交互棧裏的元素信息等內容,下一篇將會說說如何避免阻塞的腳本,lua和多線程的使用等內容。


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