LUA整合進MFC代碼
來源:互聯網
上載者:User
這幾天研究了一下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,在VS2008中,右鍵.CPP檔案,--->屬性---->C/C++----->先行編譯頭----->建立/使用先行編譯頭----->選擇 :不使用先行編譯頭 這步做完就馬上編譯一下,應該是沒問題的了!
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和多線程的使用等內容。