LUA是一種體積小,速度快的腳本語言。腳本語言雖然性能上和C++這樣的Naitive語言相比差一點,但是開發速度快,可以方便的更新代碼等,近年來受到了越來越多開發者的重視。
在SOUI框架中,我把腳本模塊參考CEGUI抽象出一個獨立的腳本接口,方便實現各種腳本語言的對接。
下面簡單介紹一下在SOUI中實現的LUA腳本模塊的實現。
在客戶端程序中使用腳本語言一個基本的需求就是C++代碼和腳本代碼的相互調用,即C++代碼可以調用腳本代碼,腳本代碼也要能夠方便的調用C++代碼。
LUA腳本原生提供了訪問C函數的方法,只需要簡單的調用幾行代碼就可以方便的把C函數注冊到LUA函數空間中,但是並沒有原生提供訪問C++對象的能力。
但是LUA中實現的metatable能夠很好的模擬C++的OOP能力,這也為導出C++對象到LUA提供了可能。
目前已經有很多方法可以將C++對象導出到LUA,比如luabind,tolua++, fflua及本文中用到的lua_tinker。
luabind據說體積比較大,tolua++好像已經沒人維護了,目前只支持lua 5.1.4,fflua是國內一個大神的作品,使用簡單,只是我使用中碰到一點問題,最后還是選擇了lua_tinker。
lua_tinker是一個韓國大神的作品,雖然作者本人沒有維護了,但是代碼相對比較簡單易懂,國內有不少高手都對它進行了擴展。
在SOUI中使用的是官方的0.5c版本上結合網友修改的版本,實現對lua 5.2.3的支持。
言歸正傳,下面說說如何使用lua_tinker導出SOUI對外到LUA。
要使用LUA,首先當然要有一份LUA內核代碼,這里用的lua 5.2.3。
為了在SOUI中使用LUA,我們還需要使用LUA內核實現一個SOUI::IScriptModuler接口:
namespace SOUI { class SWindow; /*! \brief Abstract interface required for all scripting support modules to be used with the SOUI system. */ struct IScriptModule : public IObjRef { /** * GetScriptEngine * @brief 獲得腳本引擎的指針 * @return void * -- 腳本引擎的指針 * Describe */ virtual void * GetScriptEngine () = 0; /************************************************************************* Abstract interface *************************************************************************/ /*! \brief Execute a script file. \param pszScriptFile String object holding the filename of the script file that is to be executed */ virtual void executeScriptFile(LPCSTR pszScriptFile) = 0; /*! \brief Execute a script buffer. \param buff buffer of the script that is to be executed \param sz size of buffer */ virtual void executeScriptBuffer(const char* buff, size_t sz) = 0; /*! \brief Execute script code contained in the given String object. \param str String object holding the valid script code that should be executed. \return Nothing. */ virtual void executeString(LPCSTR str) = 0; /*! \brief Execute a scripted global 'event handler' function. The function should take some kind of EventArgs like parameter that the concrete implementation of this function can create from the passed EventArgs based object. \param handler_name String object holding the name of the scripted handler function. \param EventArgs *pEvt SWindow based object that should be passed, by any appropriate means, to the scripted function. \return - true if the event was handled. - false if the event was not handled. */ virtual bool executeScriptedEventHandler(LPCSTR handler_name, EventArgs *pEvt)=0; /*! \brief Return identification string for the ScriptModule. If the internal id string has not been set by the ScriptModule creator, a generic string of "Unknown scripting module" will be returned. \return String object holding a string that identifies the ScriptModule in use. */ virtual LPCSTR getIdentifierString() const = 0; /*! \brief Subscribes or unsubscribe the named Event to a scripted function \param target The target EventSet for the subscription. \param uEvent Event ID to subscribe to. \param subscriber_name String object containing the name of the script function that is to be subscribed to the Event. \return */ virtual bool subscribeEvent(SWindow* target, UINT uEvent, LPCSTR subscriber_name) = 0; /** * unsubscribeEvent * @brief 取消事件訂閱 * @param SWindow * target -- 目標窗口 * @param UINT uEvent -- 目標事件 * @param LPCSTR subscriber_name -- 腳本函數名 * @return bool -- true操作成功 * Describe */ virtual bool unsubscribeEvent(SWindow* target, UINT uEvent, LPCSTR subscriber_name ) = 0; }; struct IScriptFactory : public IObjRef { virtual HRESULT CreateScriptModule(IScriptModule ** ppScriptModule) = 0; }; }
實現上述接口后,SOUI就可以用這個接口和腳本交互。
導出SOUI對象通常應該在IScriptModule的實現類的構造中執行。
使用lua_tinker導出C++對象非常簡單,下面看一下scriptmodule-lua是如何導出SOUI中使用的幾個C++對象的:
//導出基本結構體類型 UINT rgb(int r,int g,int b) { return RGBA(r,g,b,255); } UINT rgba(int r,int g, int b, int a) { return RGBA(r,g,b,a); } BOOL ExpLua_Basic(lua_State *L) { try{ lua_tinker::def(L,"RGB",rgb); lua_tinker::def(L,"RGBA",rgba); //POINT lua_tinker::class_add<POINT>(L,"POINT"); lua_tinker::class_mem<POINT>(L, "x", &POINT::x); lua_tinker::class_mem<POINT>(L, "y", &POINT::y); //RECT lua_tinker::class_add<RECT>(L,"RECT"); lua_tinker::class_mem<RECT>(L, "left", &RECT::left); lua_tinker::class_mem<RECT>(L, "top", &RECT::top); lua_tinker::class_mem<RECT>(L, "right", &RECT::right); lua_tinker::class_mem<RECT>(L, "bottom", &RECT::bottom); //SIZE lua_tinker::class_add<SIZE>(L,"SIZE"); lua_tinker::class_mem<SIZE>(L, "cx", &SIZE::cx); lua_tinker::class_mem<SIZE>(L, "cy", &SIZE::cy); //CPoint lua_tinker::class_add<CPoint>(L,"CPoint"); lua_tinker::class_inh<CPoint,POINT>(L); lua_tinker::class_con<CPoint>(L,lua_tinker::constructor<CPoint,LONG,LONG>); //CRect lua_tinker::class_add<CRect>(L,"CRect"); lua_tinker::class_inh<CRect,RECT>(L); lua_tinker::class_con<CRect>(L,lua_tinker::constructor<CRect,LONG,LONG,LONG,LONG>); lua_tinker::class_def<CRect>(L,"Width",&CRect::Width); lua_tinker::class_def<CRect>(L,"Height",&CRect::Height); lua_tinker::class_def<CRect>(L,"Size",&CRect::Size); lua_tinker::class_def<CRect>(L,"IsRectEmpty",&CRect::IsRectEmpty); lua_tinker::class_def<CRect>(L,"IsRectNull",&CRect::IsRectNull); lua_tinker::class_def<CRect>(L,"PtInRect",&CRect::PtInRect); lua_tinker::class_def<CRect>(L,"SetRectEmpty",&CRect::SetRectEmpty); lua_tinker::class_def<CRect>(L,"OffsetRect",(void (CRect::*)(int,int))&CRect::OffsetRect); //CSize lua_tinker::class_add<CSize>(L,"CSize"); lua_tinker::class_inh<CSize,SIZE>(L); lua_tinker::class_con<CSize>(L,lua_tinker::constructor<CSize,LONG,LONG>); return TRUE; }catch(...) { return FALSE; } }
#include <core/swnd.h> //定義一個從SObject轉換成SWindow的方法 SWindow * toSWindow(SObject * pObj) { return sobj_cast<SWindow>(pObj); } BOOL ExpLua_Window(lua_State *L) { try{ lua_tinker::def(L,"toSWindow",toSWindow); lua_tinker::class_add<SWindow>(L,"SWindow"); lua_tinker::class_inh<SWindow,SObject>(L); lua_tinker::class_con<SWindow>(L,lua_tinker::constructor<SWindow>); lua_tinker::class_def<SWindow>(L,"GetContainer",&SWindow::GetContainer); lua_tinker::class_def<SWindow>(L,"GetRoot",&SWindow::GetRoot); lua_tinker::class_def<SWindow>(L,"GetTopLevelParent",&SWindow::GetTopLevelParent); lua_tinker::class_def<SWindow>(L,"GetParent",&SWindow::GetParent); lua_tinker::class_def<SWindow>(L,"DestroyChild",&SWindow::DestroyChild); lua_tinker::class_def<SWindow>(L,"GetChildrenCount",&SWindow::GetChildrenCount); lua_tinker::class_def<SWindow>(L,"FindChildByID",&SWindow::FindChildByID); lua_tinker::class_def<SWindow>(L,"FindChildByNameA",(SWindow* (SWindow::*)(LPCSTR,int))&SWindow::FindChildByName); lua_tinker::class_def<SWindow>(L,"FindChildByNameW",(SWindow* (SWindow::*)(LPCWSTR,int ))&SWindow::FindChildByName); lua_tinker::class_def<SWindow>(L,"CreateChildrenFromString",(SWindow* (SWindow::*)(LPCWSTR))&SWindow::CreateChildren); lua_tinker::class_def<SWindow>(L,"GetTextAlign",&SWindow::GetTextAlign); lua_tinker::class_def<SWindow>(L,"GetWindowRect",(void (SWindow::*)(LPRECT))&SWindow::GetWindowRect); lua_tinker::class_def<SWindow>(L,"GetWindowRect2",(CRect (SWindow::*)())&SWindow::GetWindowRect); lua_tinker::class_def<SWindow>(L,"GetClientRect",(void (SWindow::*)(LPRECT))&SWindow::GetClientRect); lua_tinker::class_def<SWindow>(L,"GetClientRect2",(CRect (SWindow::*)())&SWindow::GetClientRect); lua_tinker::class_def<SWindow>(L,"GetWindowText",&SWindow::GetWindowText); lua_tinker::class_def<SWindow>(L,"SetWindowText",&SWindow::SetWindowText); lua_tinker::class_def<SWindow>(L,"SendSwndMessage",&SWindow::SSendMessage); lua_tinker::class_def<SWindow>(L,"GetID",&SWindow::GetID); lua_tinker::class_def<SWindow>(L,"SetID",&SWindow::SetID); lua_tinker::class_def<SWindow>(L,"GetUserData",&SWindow::GetUserData); lua_tinker::class_def<SWindow>(L,"SetUserData",&SWindow::SetUserData); lua_tinker::class_def<SWindow>(L,"GetName",&SWindow::GetName); lua_tinker::class_def<SWindow>(L,"GetSwnd",&SWindow::GetSwnd); lua_tinker::class_def<SWindow>(L,"InsertChild",&SWindow::InsertChild); lua_tinker::class_def<SWindow>(L,"RemoveChild",&SWindow::RemoveChild); lua_tinker::class_def<SWindow>(L,"IsChecked",&SWindow::IsChecked); lua_tinker::class_def<SWindow>(L,"IsDisabled",&SWindow::IsDisabled); lua_tinker::class_def<SWindow>(L,"IsVisible",&SWindow::IsVisible); lua_tinker::class_def<SWindow>(L,"SetVisible",&SWindow::SetVisible); lua_tinker::class_def<SWindow>(L,"EnableWindow",&SWindow::EnableWindow); lua_tinker::class_def<SWindow>(L,"SetCheck",&SWindow::SetCheck); lua_tinker::class_def<SWindow>(L,"SetOwner",&SWindow::SetOwner); lua_tinker::class_def<SWindow>(L,"GetOwner",&SWindow::GetOwner); lua_tinker::class_def<SWindow>(L,"Invalidate",&SWindow::Invalidate); lua_tinker::class_def<SWindow>(L,"InvalidateRect",(void (SWindow::*)(LPCRECT))&SWindow::InvalidateRect); lua_tinker::class_def<SWindow>(L,"AnimateWindow",&SWindow::AnimateWindow); lua_tinker::class_def<SWindow>(L,"GetScriptModule",&SWindow::GetScriptModule); lua_tinker::class_def<SWindow>(L,"Move2",(void (SWindow::*)(int,int,int,int))&SWindow::Move); lua_tinker::class_def<SWindow>(L,"Move",(void (SWindow::*)(LPCRECT))&SWindow::Move); lua_tinker::class_def<SWindow>(L,"FireCommand",&SWindow::FireCommand); lua_tinker::class_def<SWindow>(L,"GetDesiredSize",&SWindow::GetDesiredSize); lua_tinker::class_def<SWindow>(L,"GetWindow",&SWindow::GetWindow); return TRUE; }catch(...) { return FALSE; } }
還是很簡單吧?!
這里有兩點需要注意:
前面的代碼里一般是導出全局函數,成員函數及成員變量,但是類的靜態成員函數是不能用上面的方法導出的,下面看一下靜態函數如何處理:
BOOL ExpLua_App(lua_State *L) { try{ lua_tinker::class_add<SApplication>(L,"SApplication"); lua_tinker::class_def<SApplication>(L,"AddResProvider",&SApplication::AddResProvider); lua_tinker::class_def<SApplication>(L,"RemoveResProvider",&SApplication::RemoveResProvider); lua_tinker::class_def<SApplication>(L,"Init",&SApplication::Init); lua_tinker::class_def<SApplication>(L,"GetInstance",&SApplication::GetInstance); lua_tinker::class_def<SApplication>(L,"CreateScriptModule",&SApplication::CreateScriptModule); lua_tinker::class_def<SApplication>(L,"SetScriptModule",&SApplication::SetScriptFactory); lua_tinker::class_def<SApplication>(L,"GetTranslator",&SApplication::GetTranslator); lua_tinker::class_def<SApplication>(L,"SetTranslator",&SApplication::SetTranslator); lua_tinker::def(L,"theApp",&SApplication::getSingletonPtr); return TRUE; }catch(...) { return FALSE; } }
注意上面導出SApplication::getSingletonPtr使用的方法,實際使用的是和導出全局函數一樣的方法,因此在腳本中調用的時候也只能和全局函數一樣調用,這一點和C++調用靜態函數是不同的。
第二個需要注意的地方就是,使用lua_tinker導出的C++類如果是多繼承的,那么只能導出一個基類,而且這個基類必須是第一個基類。
例如SWindow類,它從多個基類繼承而來,但只能使用lua_tinker::class_inh來聲明第一個基類SObject,如果把SWindow的繼承順序調整一下,在LUA腳本里獲得SWindow對象后也訪問不了SObject的方法,這一點需要特別注意。
注:上面這個問題是我搜索好長時間才發現的,但也沒有完全解決問題,本來想在導出SHostWnd時聲明繼承自SWindow,盡管把SWindow放到了繼承的第一位,但是在LUA腳本中用SHostWnd對象訪問SWindow方法仍然失敗,不知道什么原因,有興趣的朋友可以研究一下。
上面介紹了如何導出C++對象到LUA空間,下面介紹一下在LUA腳本中如何使用這些C++對象:
所有的C++對象導出到LUA后都將對應一個metatable,可以使用"."來訪問table中的成員變量(映射了C++對象的成員變量),也可以使用“:”來訪問table中的成員函數(映射了C++對象函數),全局函數則直接使用函數名調用。
例如上面導出的CRect對象,在LUA腳本中使用如下:
function test(arg) local rc = CRect(0,0,100,100); local wid = rc:Width(); --訪問成員函數Width() local x1 = rc.left;--訪問基類對象RECT的成員變量left end
更多操作請參考SOUI的demo