tolua总结(二)

C++调用lua

本节我将一步一步带领大家完成c++调用lua函数并接受lua的返回值,通过分析调用的方式来封装一个类,最终封装完成的类并不是最优的,但应该能够满足一般的项目中对lua调用的功能。不足之处欢迎大家给予指正。

 

1    基本概念

1.1      栈

c++调用lua是通过一个抽象的栈来实现数据的交换的。C++调用lua时,首先需要把lua函数需要的参数压入这个抽象的栈中,如果c++想要从lua中获取数据,则lua需要先把数据压入栈中,然后c++从栈中取得需要的数据。Lua是以严格的LIFO规则来操作栈的,即后进先出原则,而c++则可以操作栈上的任何一个元素。

2    常用函数

  • void lua_pushnil (lua_State *L);

往栈中压入空值

  • void lua_pushboolean (lua_State *L, int bool);

往栈中压入布尔型值

  • void lua_pushnumber (lua_State *L, double n);

往栈中压入double型数值

  • void lua_pushlstring (lua_State *L, const char *s, size_t length);

往栈中压入字符串,但是该字符串中可以包含'\0',字符串的长度为length

  • void lua_pushstring (lua_State *L, const char *s);

往栈中压入c风格的字符串,以'\0'结尾

  • int lua_is... (lua_State *L, int index);

用来检查栈上的一个元素是否指定的类型

  • int lua_type (lua_State *L, int index);

返回栈中元素的类型

在lua.h中定义了各个元素对应的常量:LUA_TNIL、LUA_TBOOLEAN、LUA_TNUMBER、LUA_TSTRING、LUA_TTABLE、LUA_TFUNCTION、LUA_TUSERDATA以及LUA_TTHREAD

  • int lua_toboolean (lua_State *L, int index);

将元素值转换为布尔型

  • double lua_tonumber (lua_State *L, int index);

将元素值转换为数值型

  • const char *  lua_tostring (lua_State *L, int index);

该函数返回一个指向lua栈内元素的指针,该指针是不允许修改的

切记这里的指针是指向栈上元素的,如果lua清空了栈内元素,则该指针就无效了。

  • int  lua_gettop (lua_State *L);

返回堆栈中的元素个数,它也是栈顶元素的索引

  • void lua_settop (lua_State *L, int index);

设置栈顶为一个指定的值

如果原栈顶大于新栈顶,则顶部的值被丢弃,如果原栈顶小于新栈顶,则将多出的元素赋nil。lua_settop(L,0)清空堆栈。

你也可以用负数索引作为调用lua_settop的参数;那将会设置栈顶到指定的索引。利用这种技巧,API提供了下面这个宏,它从堆栈中弹出n个元素:

#define lua_pop(L,n)  lua_settop(L, -(n)-1)

Ø  void lua_pushvalue (lua_State *L, int index);

将指定元素的一个备份拷贝到栈顶

Ø  void lua_replace (lua_State *L, int index);
移除指定元素,其上的其它元素依次下移
Ø  void lua_insert (lua_State *L, int index); 
将栈顶的元素移动到指定的索引处,其它元素依次上移
Ø  void lua_replace (lua_State *L, int index); 
将栈顶的值替换指定索引的元素,其它元素不变

3    一个简单的例子

该例子最终是通过c++代码调用lua脚本实现加法的操作并把返回值打印到控制台上。以下是代码:

test.cpp

复制代码
#include <iostream>
#include "lua.hpp"
using namespace std;

int main()
{
    int iRet = 0;
    double iValue = 0;
    lua_State * L = luaL_newstate();// 创建lua状态
    if (NULL == L)
    {
        cout << "luaL_newstate()没有足够的内存分配\n" << endl;
        return 0;
    }
    iRet = luaL_dofile(L, "test.lua");// 加载并运行lua脚本
    if (0 != iRet)
    {
        cout << "luaL_dofile() failed\n" << endl;
        return 0;
    }
    lua_getglobal(L, "add"); // 把函数名压入堆栈
    
    lua_pushnumber(L, 2);
    lua_pushnumber(L, 3);
    iRet = lua_pcall(L, 2, 1, 0); // 调用函数
    if (0 != iRet)
    {
        printf("lua_pcall failed:%s\n",lua_tostring(L,-1));
        return 0;
    }
    
    if (lua_isnumber(L, -1) == 1)
    {
        iValue = lua_tonumber(L, -1);
    }
    cout << iValue << endl;
    
    lua_close(L);
    return 0;
}
复制代码



test.lua

function add (x,y)
    return x+y
end



编译:g++ -o test test.cpp -ltolua++

运行结果:5

以下是对该例子中用到的函数的解释:

  • lua_State *luaL_newstate (void);

该函数用于创建一个新的lua状态,当内存分配错误的时候返回NULL

  • int luaL_loadfile (lua_State *L, const char *filename);

该函数加载lua文件,但不运行。

  • int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc);

该函数用于调用lua的函数,nargs是入参个数,nresults是出参个数,如果errfunc为0,表示把出错的信息放入堆栈中,如果不为0,则该值代表一个错误处理函数在lua堆栈中的索引。函数成功时返回0,错误时返回如下三个值:

LUA_ERRRUN:运行时错误;

LUA_ERRMEM:内存分配错误。对于这样的错误,Lua不调用错误处理函数;

LUA_ERRERR:运行错误处理函数的错误。

  • int luaL_dofile (lua_State *L, const char *filename);

加载并运行指定的文件,正确的时候返回0,错误的时候返回1。该函数在lua中被定义为一个宏:

(luaL_loadfile(L, filename) || lua_pcall(L, 0, LUA_MULTRET, 0))

  • void lua_getglobal (lua_State *L, const char *name);

将函数名压入到堆栈中,它被定义为如下宏:

#define lua_getglobal(L,s)  lua_getfield(L, LUA_GLOBALSINDEX, s)

  • void lua_close (lua_State *L);

该函数销毁lua状态的所有对象,如果是守护进程或web服务器,则需要在使用完后尽快释放,以避免过大。

如果想直接验证lua脚本可以按如下步骤操作:

/home/tolua/test#lua

Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio

> dofile("test.lua")

> print (add(1,4))

5

4    封装

让我们仔细研究下这个简单的例子,是不是发现了很多共同点呢?我们完全可以利用 c++的特性对其进行封装,这样我们再调用lua的时候,就可以直接调用我们封装好的类啦。

CToLua.h

复制代码
#ifndef    CTOLUA_H_
#define  CTOLUA_H_

#include "lua.hpp"
#include <iostream>
using namespace std;

class CToLua
{
public:
    CToLua();
    ~CToLua();
    bool loadLuaFile(const char* pFileName); //加载指定的Lua文件
    double callFileFn(const char* pFunctionName, const char* format, ...); //执行指定Lua文件中的函数
    lua_State* getState();
private:
    int parseParameter(const char* format, va_list& arg_ptr);
    lua_State* m_pState;
};

#endif // CTOLUA_H_
复制代码


复制代码
#include "CToLua.h"

extern "C"
{
#include "tolua++.h"
}

CToLua::CToLua()
{
    m_pState = luaL_newstate();
    if (NULL == m_pState)
    {
        printf("luaL_newstate()没有足够的内存分配\n");
    }
    else
    {
        luaopen_base(m_pState);
    }
}

CToLua::~CToLua()
{
    if (NULL != m_pState)
    {
        lua_close(m_pState);
        m_pState = NULL;
    }
}

bool CToLua::loadLuaFile(const char* pFileName)
{
    int iRet = 0;
    if (NULL == m_pState)
    {
        printf("[CToLua::LoadLuaFile]m_pState is NULL.\n");
        return false;
    }

    iRet = luaL_dofile(m_pState, pFileName);
    if (iRet != 0)
    {
        printf("[CToLua::LoadLuaFile]luaL_dofile(%s) is error(%d)(%s).\n",
            pFileName, iRet, lua_tostring(m_pState, -1));
        lua_pop(m_pState, 1); // 及时清理堆栈
        return false;
    }

    return true;
}

double CToLua::callFileFn(const char* pFunctionName, const char* format, ...)
{
    int iRet = 0;
    double iValue = 0;

    int iTop = lua_gettop(m_pState);
    lua_pop(m_pState, iTop); // 清栈

    if(NULL == m_pState)
    {
        printf("[CLuaFn::CallFileFn]m_pState is NULL.\n");
        return 0;
    }

    lua_getglobal(m_pState, pFunctionName);

    va_list arg_ptr;
    va_start(arg_ptr, format);
    iRet = parseParameter(format, arg_ptr);
    va_end(arg_ptr);

    iRet = lua_pcall(m_pState, iRet, 1, 0);
    if (iRet != 0)
    {
        printf("[CLuaFn::CallFileFn]call function(%s) error(%d).\n", pFunctionName, iRet);
        return 0;
    }

    if (lua_isnumber(m_pState, -1) == 1)
    {
        iValue = lua_tonumber(m_pState, -1);
    }

    return iValue;
}

int CToLua::parseParameter(const char* format, va_list& arg_ptr)
{
    int iRet = 0;
    char* pFormat = (char*) format;
    while (*pFormat != '\0')
    {
        if ('%' == *pFormat)
        {
            ++pFormat;
            switch (*pFormat)
            {
            case 'f':
                lua_pushnumber(m_pState, va_arg( arg_ptr, double));
                break;
            case 'd':
            case 'i':
                lua_pushnumber(m_pState, va_arg( arg_ptr, int));
                break;
            case 's':
                lua_pushstring(m_pState, va_arg( arg_ptr, char*));
                break;
            case 'z':
                lua_pushlightuserdata(m_pState, va_arg( arg_ptr, void*));
                break;
            default:
                break;
            }
            ++iRet;
        }
        ++pFormat;
    }
    return iRet;
}

lua_State* CToLua::getState()
{
    return m_pState;
}
复制代码

 

main.cpp

复制代码
#include "CToLua.h"

int main()
{
    CToLua tolua;
    tolua.loadLuaFile("C:\\c++\\lua\\test.lua");
    double iValue = tolua.callFileFn("add", "%d%f", 20, 3.69); // 23.69
    cout << iValue << endl;
    iValue = tolua.callFileFn("sub", "%f%i", 23.69,20); // 3.69
    cout << iValue << endl;
    return 0;
}
复制代码

 

test.lua

复制代码
function add (x,y)
        return x+y
end

function sub (x,y)
        return x-y
end
复制代码

 

callFileFn方法使用类似printf,%d或%i代表整数,%f代表浮点数,%s代表字符串,%z代表自定义类型。

由于lua可以支持多返回值,所以原先的设计思想是返回一个vector,里边包含一个结构体,有两个成员,一个是string type,一个是void* pValue。但当我实现了后,发现虽然支持了多个返回值了,但是使用起来却不是那么方便。所以索性就不实现这种返回多个返回值的接口了。函数的返回值固定,只返回double类型的值,如果想要多个返回值,可以传自定义参数来实现。

 

在这里推荐一下linux下的eclipse c++开发环境,本次代码编写就是在该环境下进行的,使用起来还算比较舒服。搭建方式参考http://www.cnblogs.com/osyun/archive/2011/12/05/2276412.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
 本套课程分为基础与中级两部分,分别就lua语言的各方面知识点进行探讨,学习完本套课程,对于后续Xlua(Tolua等框架)技术的学习提供强大的语言技术保证。       本套lua课程采用入门与商业级两种开发IDE进行教学:入门级的SciTE内置IDE与商业级的IDEA lua插件。本套课程学习完毕,对于除了传统手游外,在VR、AR、商业级大型应用程序、嵌入式设备开发等领域都有较强的指导作用。           《lua中级篇》分为:“函数的进阶”、“字符串进阶”、“Table进阶”、“元表”、“OOP面向对象”、“协同程序”、“IO操作”、“调试与运行”等八个大的章节,详细深入讲解lua开发的方方面面。        内容包含lua可变参数、闭包、模块、函数尾调用、字符串模式匹配、字符串不变性原理、矩阵、链表、元表详解与应用、协同的生命周期与生产消费者问题、lua文件各种读写操作、lua执行外部代码与错误异常处理垃圾收集机制等。       最后,lua中级篇的学习,对于广大学员开发商业级lua热更新技术,具有不可替代的重要作用! 热更新系列(技术含量:中高级):B:《热更新框架设计之Xlua基础视频课程》https://edu.csdn.net/course/detail/27110C:《热更新框架设计之热更流程与热补丁技术》https://edu.csdn.net/course/detail/27118D:《热更新框架设计之客户端热更框架(上)》https://edu.csdn.net/course/detail/27132E:《热更新框架设计之客户端热更框架(中)》https://edu.csdn.net/course/detail/27135F:《热更新框架设计之客户端热更框架(下)》https://edu.csdn.net/course/detail/27136 

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值