第二十四篇:导出SOUI对象到LUA脚本 lua脚本入门

   2023-02-08 学习力0
核心提示:LUA是一种体积小,速度快的脚本语言。脚本语言虽然性能上和C++这样的Naitive语言相比差一点,但是开发速度快,可以方便的更新代码等,近年来受到了越来越多开发者的重视。在SOUI框架中,我把脚本模块参考CEGUI抽象出一个独立的脚本接口,方便实现各种脚本语言

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

 
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • LUA解析json小demo
    需要修改的json数据gui-config.json{"configs": [{"server": "JP3.ISS.TF","server_port": 443,"password": "58603228","method": "aes-256-cfb","remarks": ""},{"serv
    03-16
  • 第二十三篇:在SOUI中使用LUA脚本开发界面
    像写网页一样做客户端界面可能是很多客户端开发的理想。做好一个可以实现和用户交互的动态网页应该包含两个部分:使用html做网页的布局,使用脚本如vbscript,javascript做用户交互的逻辑。当需求变化时,只需要在服务端把相关代码调整一下,用户即可看到新的
    03-16
  • windows下编译lua源码"><转>windows下编译lua源
    因为之前一直使用 lua for windows 来搭建lua的使用环境,但是最新的 lua for windows 还没有lua5.2,我又想用这个版本的lua,所以被逼无奈只能自己编一下lua源码。首先从 lua的官网 下载你想要使用的lua源码,比如我下载的就是lua5.2。解压后内容如下:
    03-16
  • lua:使用Lua处理游戏数据
    在之前lua学习:lua作配置文件里,我们学会了用lua作配置文件。其实lua在游戏开发中可以作为一个强大的保存、载入游戏数据的工具。 比如说,现在我有一份表单:data.xls用什么工具解析这个Excel文件并将数据载入游戏?我们可以使用Lua来完成这个工作。不过要
    03-16
  • 第1课 - 学习 Lua 的意义
    第1课 - 学习 Lua 的意义
    第1课 - 学习 Lua 的意义1.Lua 简介           (1) 1993年、巴西(2) 小巧精致的脚本语言,大小只有 200K(3) 用标准C语言写成,能够在所有的平台上编译运行(4) 发明的目标是嵌入在C/C++中,为应用程序提供灵活的扩展和定制功能(5) 不适合用于开发
    03-16
  • RedisTemplate 常用API+事务+陷阱+序列化+pipeline+LUA
    RedisTemplate 常用API+事务+陷阱+序列化+pipel
    https://www.jianshu.com/p/7bf5dc61ca06/https://blog.csdn.net/qq_34021712/article/details/79606551https://www.jianshu.com/p/c9f5718e58f0dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/depe
    03-08
  • Nginx动态路由的新姿势:使用Go取代lua nginx路由规则
    Nginx动态路由的新姿势:使用Go取代lua nginx路
    导语: 在Nitro 中, 我们需要一款专业的负载均衡器。 经过一番研究之后,Mihai Todor和我使用Go构建了基于Nginx、Redis 协议的路由器解决方案,其中nginx负责所有繁重工作,路由器本身并不承载流量。 这个解决方案过去一年在生产环境中运行顺畅。 以下是我
    03-08
  • cocos2d-lua 控制台输入Lua指令方便调试
    用脚本进行开发,如果不能实时去输入指令,就丧失了脚本的一大特色,所以对cocos2d-x程序稍微修改下,使其可以直接从控制台读入lua指令,方便调试。1 首先在行首加入lua的引用,如下1 #include "main.h"2 #include "AppDelegate.h"3 #include "cocos2d.h"4 #i
    02-09
  • lua_touserdata
    void *lua_touserdata(lua_State*L,intindex);如果给定索引处的值是一个完整的userdata,函数返回内存块的地址。如果值是一个lightuserdata,那么就返回它表示的指针。否则,返回NULL。例如: 在CCLuaStack::executeFunction()函数中有一段代码是用来获取c++
    02-09
  • Lua 5.2 中文参考手册
    闲来无事,发现Lua更新到了5.2.2,参考手册也更到了5.2,在网上发现只有云风翻译的5.1版,花了几天时间翻译了一些。参考手册有点长,又要随时修改,所以在github上建了项目,有需要的朋友可以看看,同时也欢迎指正。中文手册:Lua 5.2中文参考手册
    02-09
点击排行