环境配置参考上一篇博客lua环境配置 此篇主要介绍lua与C++的交互。包括基础的调用lua文件以及和lua栈相关的知识。

第一个实例

//1. 初始化Lua虚拟机
    lua_State *lua_state;
    lua_state = luaL_newstate();
    int error;
    //2. 打开所有lua的标准库
    luaL_openlibs(lua_state);
    //3. 运行脚本文件
    error = luaL_dofile(lua_state, "hello.lua");
    if (error == LUA_OK)
        std::cout << " nothing wrong" << std::endl;
    else
        std::cout << lua_tostring(lua_state, -1);
	lua_close(lua_state);

一、调用解释

1. luaL_newstate

可以理解为新建了一个环境(或者状态),用于C++和lua的交互。C++和lua之间所有的数据交互都会通过这个环境,相当于是一个桥梁的作用。

2. luaL_openlibs

上述用 luaL_newstate新建的环境中什么都没有,如果需要在hello.lua里边使用函数(例如本例中的print),则需要引入相应的库。print需要用到的是基础库base。
所以如前一篇配置文档代码里所列,也可以只导入需要的库,这样就可以忽略不需要的包。代码如下:

static const luaL_Reg lualibs[] =
    {
        { "base", luaopen_base },
        { NULL, NULL}
    };
    //3.注册Lua标准库并清空栈
    const luaL_Reg *lib = lualibs;
    for(; lib->func != NULL; lib++)
    {
        luaL_requiref(lua_state, lib->name, lib->func, 1);
        lua_pop(lua_state, 1);
    }

而本例中所用到的luaL_openlibs,是打开了所有的标准库,包括(base,io, table, math等库)。我们去查看lua的源码文件可以知道其实luaL_openlibs也是对分开的各个库进行了注册(luaL_requiref)操作。从linit.c文件中摘录的源码如下(头文件定义在lualib.h里面):

static const luaL_Reg loadedlibs[] = {
  {"_G", luaopen_base},
  {LUA_LOADLIBNAME, luaopen_package},
  {LUA_COLIBNAME, luaopen_coroutine},
  {LUA_TABLIBNAME, luaopen_table},
  {LUA_IOLIBNAME, luaopen_io},
  {LUA_OSLIBNAME, luaopen_os},
  {LUA_STRLIBNAME, luaopen_string},
  {LUA_MATHLIBNAME, luaopen_math},
  {LUA_UTF8LIBNAME, luaopen_utf8},
  {LUA_DBLIBNAME, luaopen_debug},
#if defined(LUA_COMPAT_BITLIB)
  {LUA_BITLIBNAME, luaopen_bit32},
#endif
  {NULL, NULL}
};


LUALIB_API void luaL_openlibs (lua_State *L) {
  const luaL_Reg *lib;
  /* "require" functions from 'loadedlibs' and set results to global table */
  for (lib = loadedlibs; lib->func; lib++) {
    luaL_requiref(L, lib->name, lib->func, 1);
    lua_pop(L, 1);  /* remove lib */
  }
}

在这里只要知道如果需要在lua里边用到print等基础函数,就需要在C++程序里面导入相应的库就可以了。具体对luaL_requiref的理解可以等我们讲到自己写的C++函数,并需要在lua程序里边调用时就有更深入的理解了。

3. luaL_dofile

luaL_dofile就是运行脚本文件,追踪源码可以知道它就是一个宏定义。

#define luaL_dofile(L, fn) \
	(luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0))

所以我们也可以自己写成

error = luaL_loadfile(lua_state, "hello.lua") || lua_pcall(lua_state, 0, LUA_MULTRET, 0);

我们可以看到它分为两个部分luaL_loadfile和lua_pcall。

1) luaL_loadfile

这部分是用于导入文件,同时有一些错误检查,例如找不到文件等会引发错误。如果没有错误,调用返回0,并向栈(栈的概念后面详细讲)中压入编译后的代码块。

2) lua_pcall

这个函数将代码块从栈中弹出,并运行,如果有错误,则会向栈中压入一条错误信息。比如,如果我们在程序中没有导入基本库(没有luaL_openlibs相关的库),则我们可以从栈中得到一条错误信息(lua默认不打印到标准输出,而只是压到栈中),将其读取出来并打印可以得到。

hello.lua:1: attempt to call a nil value (global 'print')Program ended with exit code: 0

如果把上述两部分分开写,大家可以尝试一下分别打印错误信息。

std::string scriptPath = "hello.lua";
    int status = luaL_loadfile(lua_state, scriptPath.c_str());
    std::cout << "return: " << status << std::endl;
    int result = 0;
    if(status == LUA_OK)
    {
         result = lua_pcall(lua_state, 0, LUA_MULTRET, 0);
        if (result == LUA_OK)
        {
            std::cout << "Nothing wrong" << std::endl;
        }
        else
        {
            std::cout << lua_tostring(lua_state, -1);
        }
    }
    else
    {
        std::cout << " Could not load the script." << std::endl;
    }

二、栈

C++和lua的交互全部通过一个栈来完成。lua的类型广泛来讲只有一种就是table,那么lua和c++之间交互的时候又互相不知道类型,所有不能直接使用,栈这种结构就比较好地解决了这个问题。栈用来保存lua的数据,当c++想向lua传入数据的时候,就需要先将数据通过AP
I转换成lua的形式,再压入栈;当c++想从lua读取数据的时候,可以从栈中获取数据,然后转换成c++的数据形式。反之,如果是从lua的角度来看,数据也都是通过栈来传递的。

1. 栈的基本结构

| n | 栈顶 | -1 |
| … | ----- | -2 |
| 2 | ------ | … |
| 1 | 栈底 | -n |
上述图是栈的序号示意图,如果从栈底往栈顶,则序号一次为1,2,…,n; 如果从栈顶往栈底往下数,则序号为-1,-2,…, -n。

2. 栈的基本操作

栈的基本操作包括压入弹出操作、访问查询操作。

1)压入弹出元素

举例:
lua_pushnumber(lua_State *L, lua_Number n)lua_pop(lua_State *L, int n) 注意,这里的lua_pop的第二个参数是个数,而不是栈的序号

2) 访问查询操作

1、检查元素类型
lua_isnumber(lua_State *L, int idx) 或者 int i = lua_type(lua_State *L, int idx) 2、遍历元素
lua_tonumber(lua_State *L, int idx),可以直接将这个结果在C++程序中打印出来

3) 其他

lua_gettop(lua_State *L)用于获得栈中元素的个数

举例

//1. 初始化Lua虚拟机
    lua_State *lua_state;
    lua_state = luaL_newstate();
//2. 压数据入栈
    lua_pushnumber(lua_state, 5);
    lua_pushstring(lua_state, "hello~ lua lua");

//3. 判断数据类型并用相应的接口转换数据
    if(lua_isnumber(lua_state, 1))
      {
          std::cout << lua_tonumber(lua_state, 1) << std::endl;
      }

    if(lua_type(lua_state, 2) == LUA_TSTRING)
        std::cout << lua_tostring(lua_state, 2) << std::endl;
// 4. 获取栈里边数据的数目
    std::cout << "There are " << lua_gettop(lua_state) << " elements in the stack!" << std::endl;

    lua_close(lua_state);

输出结果

5
hello~ lua lua
There are 2 elements in the stack!