Lua腳本在C++下的舞步(三)

上一講我把Lua基本的棧規則講了一下,然後完善了一下我的CLuaFn類。讓它可以支持任意參數數量和函數名稱的傳值。當然,這些功能是爲了今天這篇文章而鋪路的。 
看了七貓的回帖,呵呵,確實應該說一下SWIG這個工具,說真的,我對這個工具理解不深,因爲沒有怎麼用過,讀過一些關於它的文章,似乎是幫你把C++的功能封裝成一個Lua基本庫的東西,但是後來研究,他可以很輕鬆幫你把公用函數封裝成一個Lua的基本庫(類似C++的dll),但是對於我的需求而言,可能不太一樣。因爲我大量的是需要在C++裏面進行數據傳輸以及變量的交互,所以爲了緊貼C++,我需要很多關聯數據的處理。 
我是一名C++程序員,所以在很多時候,不想過多的使用Lua的特性,因爲個人感覺,Lua的語法要比C++的更加靈活。而我更希望,在函數調用的某些習慣上,遵循一些C++的規則。 
好了,廢話少說,我們先來看一個類(頭文件)。假設我們要把這個對象,傳輸給Lua進行調用。

#ifndef _TEST_H 
#define _TEST_H

class CTest 

public: 
        CTest(void); 
        ~CTest(void);

        char* GetData(); 
        void SetData(const char* pData);

private: 
        char m_szData[200]; 
}; 
#endif

這個類裏面有兩個函數,一個是GetData(),一個是SetData(),之所以這麼寫,我要讓Lua不僅能使用我的類,還可以給這個類使用參數。 
那麼,cpp文件,我們姑且這樣寫。(當然,你可以進行修改,按照你喜歡的方式寫一個方法,呵呵)

char* CTest::GetData() 

        printf(“[CTest::GetData]%s./n”, m_szData); 
        return m_szData; 
}

void CTest::SetData(const char* pData) 

        sprintf(m_szData, “%s”, pData); 
}

這是一個標準的類,我需要這個類在Lua裏面可以創造出來,並賦予數值,甚至我可以把CTest作爲一個Lua函數參數,傳給Lua函數讓它去給我處理。讓我們來看看怎麼做。如果使用標準的Lua語法,有點多,所以我就借用一下上次提到的tolua來做到這一切,我一句句的解釋。姑且我們把這些代碼放在LuaFn.cpp裏面。

static int tolua_new_CTest(lua_State* pState) 

        CTest* pTest = new CTest(); 
        tolua_pushusertype(pState, pTest, “CTest”); 
        return 1; 
}

static int tolua_delete_CTest(lua_State* pState) 

        CTest* pTest = (CTest* )tolua_tousertype(pState, 1, 0); 
        if(NULL != pTest) 
        { 
                delete pTest; 
        } 
        return 1; 
}

static int tolua_SetData_CTest(lua_State* pState) 

        CTest* pTest = (CTest* )tolua_tousertype(pState, 1, 0); 
        const char* pData = tolua_tostring(pState, 2, 0);

        if(pData != NULL && pTest != NULL) 
        { 
                pTest->SetData(pData); 
        }

        return 1; 
}

static int tolua_GetData_CTest(lua_State* pState) 

        CTest* pTest = (CTest* )tolua_tousertype(pState, 1, 0);

        if(pTest != NULL) 
        { 
                char* pData = pTest->GetData(); 
                tolua_pushstring(pState, pData); 
        }

        return 1; 
}

看看這幾個靜態函數在幹什麼。 
我要在Lua裏面使用CTest,必須讓Lua裏這個CTest對象能夠順利的創造和銷燬。tolua_new_CTest()和tolua_delete_CTest()就是幹這個的。 
tolua_pushusertype(pState, pTest, “CTest”); 這句話的意思是,將一個已經在Lua註冊的”CTest”對象指針,壓入數據棧。 
同理,CTest* pTest = (CTest* )tolua_tousertype(pState, 1, 0);是將數據棧下的對象以(CTest* )的指針形式彈出來。 
tolua_SetData_CTest()函數和tolua_GetData_CTest分別對應CTest的SetData方法和GetData()方法。因爲我們的SetData方法裏面存在變量,那麼同樣,我們需要使用const char* pData = tolua_tostring(pState, 2, 0);將參數彈出來,然後輸入到pTest->SetData(pData);對象中去,當然,你可以有更多若干個參數。隨你的喜好。這裏只做一個舉例。 
好了,你一定會問,這麼多的靜態函數,用在哪裏?呵呵,當然是給Lua註冊,當你把這些數據註冊到Lua裏面,你就可以輕鬆的在Lua中使用它們。 
讓我們看看,註冊是怎麼做到的。 
還是在CLuaFn類裏面,我們增加一個函數。比如叫做bool InitClass();

bool CLuaFn::InitClass() 

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

        tolua_open(m_pState); 
        tolua_module(m_pState, NULL, 0); 
        tolua_beginmodule(m_pState, NULL);

        tolua_usertype(m_pState, “CTest”); 
        tolua_cclass(m_pState, “CTest”, “CTest”, “”, tolua_delete_CTest);

        tolua_beginmodule(m_pState, “CTest”); 
        tolua_function(m_pState, “new”, tolua_new_CTest); 
        tolua_function(m_pState, “SetData”, tolua_SetData_CTest); 
        tolua_function(m_pState, “GetData”, tolua_GetData_CTest); 
        tolua_endmodule(m_pState);

        tolua_endmodule(m_pState);

        return true; 
}

上面的代碼,就是我把上面的幾個靜態函數,綁定到Lua的基礎對象中去。 
tolua_beginmodule(m_pState, “CTest”);是隻註冊一個模塊,比如,我們管CTest叫做”CTest”,保持和C++的名稱一樣。這樣在Lua的對象庫中就會多了一個CTest的對象描述,等同於string,number等等基本類型,同理,你也可以用同樣的方法,註冊你的MFC類。是不是有點明白了?這裏要注意,tolua_beginmodule()和tolua_endmodule()對象必須成對出現,如果出現不成對的,你註冊的C++類型將會失敗。 
tolua_function(m_pState, “SetData”, tolua_SetData_CTest);指的是將Lua裏面CTest對象的”SetData”綁定到你的tolua_SetData_CTest()函數中去。

好的,讓我們來點激動人心的東西。還記得我們的Simple.lua的文件麼。我們來改一下它。

function func_Add(x, y) 
  local test = CTest:new(); 
  test:SetData(“I’m freeeyes!”); 
  test:GetData(); 
  return x..y; 
end

我在這個函數裏面,New了一個CTest對象,並進行賦值操作,最後把結果打印在屏幕上。你或許會問,最後一句不是x+y麼,怎麼變成了x..y,呵呵,在Lua中,..表示聯合的意思,就好比在C++裏面, string strName += “freeeyes”。原來覺得x+y有點土,索性返回一個兩個字符串的聯合吧。 
好了,我們已經把我們的這個CTest類註冊到了Lua裏面,讓我們來調用一下吧。修改一下Main函數。變成以下的樣子。

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

        CLuaFn LuaFn;

        LuaFn.InitClass();

        LuaFn.LoadLuaFile(“Sample.lua”);

        CParamGroup ParamIn; 
        CParamGroup ParamOut;

        char szData1[20] = {‘/0′}; 
        sprintf(szData1, “[freeeyes]“); 
        _ParamData* pParam1 = new _ParamData(szData1, “string”, (int)strlen(szData1)); 
        ParamIn.Push(pParam1);

        char szData2[20] = {‘/0′}; 
        sprintf(szData2, “[shiqiang]“); 
        _ParamData* pParam2 = new _ParamData(szData2, “string”, (int)strlen(szData2)); 
        ParamIn.Push(pParam2); 
        char szData3[40] = {‘/0′}; 
        _ParamData* pParam3 = new _ParamData(szData3, “string”, 40); 
        ParamOut.Push(pParam3);

        LuaFn.CallFileFn(“func_Add”, ParamIn, ParamOut);

        char* pData = (char* )ParamOut.GetParam(0)->GetParam(); 
        printf(“[Main]Sum = %s./n”, pData);

        getchar();

        return 0; 
}

如果你完全按照我的,你就可以編譯你的工程了,運行一下,看看是啥結果?

[CTest::GetData]I’m freeeyes!. 
[Main]Sum = [freeeyes][shiqiang]. 
看看,是不是和我輸出的一樣?

呵呵,有意思吧,你已經可以在Lua裏面用C++的函數了,那麼咱們再增加一點難度,比如,我有一個CTest對象,要作爲一個參數,傳輸給func_Add()執行,怎麼辦? 
很簡單,如果你對上面的代碼仔細閱讀,你會發現下面的代碼一樣簡潔。爲了支持剛纔要說的需求,我們需要把Sample.lua再做一點修改。

function func_Add(x, y, f) 
  f:SetData(“I’m freeeyes!”); 
  f:GetData(); 
  return x..y; 
end

f假設就是我們要傳入的CTest對象。我們要在Lua裏面使用它。(我們的CLuaFn都不用改,把main函數稍微改一下即可,來看看怎麼寫。)

// LuaSample.cpp : 定義控制檯應用程序的入口點。 
//

#include “stdafx.h” 
#include “LuaFn.h”

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

        CLuaFn LuaFn;

        LuaFn.InitClass();

        LuaFn.LoadLuaFile(“Sample.lua”);

        CParamGroup ParamIn; 
        CParamGroup ParamOut;

        char szData1[20] = {‘/0′}; 
        sprintf(szData1, “[freeeyes]“); 
        _ParamData* pParam1 = new _ParamData(szData1, “string”, (int)strlen(szData1)); 
        ParamIn.Push(pParam1);

        char szData2[20] = {‘/0′}; 
        sprintf(szData2, “[shiqiang]“); 
        _ParamData* pParam2 = new _ParamData(szData2, “string”, (int)strlen(szData2)); 
        ParamIn.Push(pParam2);

        //只追加了這裏 
        CTest* pTest = new CTest(); 
        _ParamData* pParam3 = new _ParamData(pTest, “CTest”, sizeof(CTest)); 
        ParamIn.Push(pParam3); 
       //追加結束 
        char szData4[40] = {‘/0′}; 
        _ParamData* pParam4 = new _ParamData(szData4, “string”, 40); 
        ParamOut.Push(pParam4);

        LuaFn.CallFileFn(“func_Add”, ParamIn, ParamOut);

        char* pData = (char* )ParamOut.GetParam(0)->GetParam(); 
        printf(“[Main]Sum = %s./n”, pData);

        getchar();

        return 0; 
}

好了,就這麼點代碼,改好了,我們再Build一下,然後點擊運行。看看輸出結果,是不是和以前的一樣? 
恩,是不是有點興奮了?你成功的讓Lua開始調用你的C++對象了!並且按照你要的方式執行!還記得我曾在第一篇文章裏面許諾過,我會讓你畫出一個MFC窗體麼?呵呵,如果你到現在依然覺得很清晰的話,說明你的距離已經不遠了。

既然已經到了這裏,我們索性再加點難度,如果我要把CTest作爲一個對象返回回來怎麼做?很簡單,且看。

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

        CLuaFn LuaFn;

        LuaFn.InitClass();

        LuaFn.LoadLuaFile(“Sample.lua”);

        CParamGroup ParamIn; 
        CParamGroup ParamOut;

        char szData1[20] = {‘/0′}; 
        sprintf(szData1, “[freeeyes]“); 
        _ParamData* pParam1 = new _ParamData(szData1, “string”, (int)strlen(szData1)); 
        ParamIn.Push(pParam1);

        char szData2[20] = {‘/0′}; 
        sprintf(szData2, “[shiqiang]“); 
        _ParamData* pParam2 = new _ParamData(szData2, “string”, (int)strlen(szData2)); 
        ParamIn.Push(pParam2);

        CTest* pTest = new CTest(); 
        _ParamData* pParam3 = new _ParamData(pTest, “CTest”, sizeof(CTest)); 
        ParamIn.Push(pParam3); 
        CTest* pTestRsult = NULL; 
        _ParamData* pParam4 = new _ParamData(pTestRsult, “CTest”, sizeof(pTestRsult)); 
        ParamOut.Push(pParam4);

        LuaFn.CallFileFn(“func_Add”, ParamIn, ParamOut);

        //接受Lua返回參數爲CTest類型,並調用其中的方法。 
        pTestRsult = (CTest* )ParamOut.GetParam(0)->GetParam(); 
        pTestRsult->GetData();

        getchar();

        return 0; 
}

好,編譯,執行。呵呵,看到了吧。

看到這裏,如果你能看的明白,說明你已經對Lua如何調用C++接口,以及C++如何調用Lua有了一定的理解。當然,我寫的這個類也不是很完善,不過做一半的Lua開發,應該是夠用了。以以上的方式,你可以使用Lua駕馭你的C++代碼。 
好了,咱們既然已經說到這裏了,再深一步,如果我的類是繼承的,怎麼辦?呵呵,很好的問題。 
比如,我的CTest繼承了一個CBase,我的CBase又繼承了一個。。。 
在Lua裏面,一樣簡單,我拿MFC的例子來舉例吧,想必大家更喜歡看。 
比如 CCmdTarget繼承自CObject。 
那麼我在註冊的時候可以這麼寫。

tolua_cclass(tolua_S, “CCmdTarget”,      ”CCmdTarget”,      ”CObject”,            NULL); 
這個表示CCmdTarget繼承自CObject對象。 
當然,MFC裏面還會有很多類型,比如常數,Lua一樣能處理。 
舉個例子說。

tolua_constant(tolua_S, “ES_AUTOHSCROLL”,   ES_AUTOHSCROLL); 
這樣註冊,你就可以在 Lua裏面使用ES_AUTOHSCROLL這個常數,它會自動綁定ES_AUTOHSCROLL這個C++常數對象。

呵呵,說了這麼多,讓我們來點實際的。我給大家一個我以前寫的MFC封裝類(由於代碼太多,我變成附件給大家),你們可以調用,當然,如果你有興趣,就用我的MFC類,來做一個你喜歡的窗體吧,當然,你必須要用Lua腳本把它畫出來,作爲最後的考驗,呵呵。

附帶全部工程(附帶Lua及tolua++)

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