轉載地址: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和多線程的使用等內容。