C/C++编程:Lua与C/C++的交互

理论

Lua是一门动态脚本语言,运行依托于宿主语言,可以是C、C++、C#、golang等,只要实现了Lua解释器就可以。所以,Lua从设计来讲就是动态脚本语言,正是因为它是解释性语言,所以它更充当了这些宿主语言的“缝合”作用,是为“胶水”性语言

lua中的栈

lua中的栈是一个很奇特的数据结构,普通的栈只有一排索引,但是在lua中有两排索引,正数1索引的位置在栈底,负数-1索引的位置在栈顶。如下图所示。

C/C++编程:Lua与C/C++的交互_第1张图片

根据结构图,我们不需要知道栈的大小,我们就可以确定的栈顶和栈底的位置

  • 当索引是1的时候对应的是栈底
  • 当索引是-1的时候对于的是栈顶。

常用的lua api

  • lua_State* L=luaL_newstate();luaL_newstate()函数返回一个指向堆栈的指针
  • lua_createtable(L,0,0);新建并压入一张表
  • lua_pushstring(L,0,0);压入一个字符串
  • lua_pushnumber(L,0,0);压入一个数字
  • lua_tostring(L,1);取出一个字符串
  • lua_tointeger(L,1);取出数字
  • double b=lua_tonumber();取出一个double类型的数字
  • lua_load()函数 当这个函数返回0时表示加载
  • luaL_loadfile(filename) 这个函数也是只允许加载lua程序文件,不执行lua文件。它是在内部去用lua_load()去加载指定名为filename的lua程序文件。当返回0表示没有错误。
  • luaL_dofile 这个函数不仅仅加载了lua程序文件,还执行lua文件。返回0表示没有错误。
  • lua_push*(L,data)压栈
  • lua_to*(L,index)取值
  • lua_pop(L,count)出栈
  • lua_close(L);释放lua资源
  • lua_getglobal(L, “val”);//获取全局变量的val的值,并将其放入栈顶

Lua中调用CPP函数

Lua可以调用C函数的能力极大的提高了Lua的可扩展性和可用性。对有些和操作系统相关的功能,或者是对效率要求较高的模块,我们完全可以通过C函数来实现,之后再通过Lua调用指定的C函数。

对于那些可被Lua调用的C函数而言,其接口必须遵循Lua要求的形式,即typedef int (*lua_CFunction)(lua_State* L).。解析一下该形式:

  • 该函数类型仅仅包含一个表示Lua环境的指针作为其唯一的参数,实现者可以通过该指针进一步获取Lua代码中实际传入的参数。
  • 返回值是整型,表示该C函数将返回给Lua代码的返回值数量,如果没有返回值,则return 0即可
  • 需要说明的是,C函数无法直接将真正的返回值返回给Lua代码,而是通过虚拟栈来传递Lua代码和C函数之间的调用参数和返回值的

可能出现的错误

a.cpp:8: 警告:不建议使用从字符串常量到‘char*’的转换
/tmp/ccij5HeF.o:在函数‘main’中:
a.cpp:(.text+0xe):对‘luaL_newstate()’未定义的引用
a.cpp:(.text+0x24):对‘luaL_openlibs(lua_State*)’未定义的引用
a.cpp:(.text+0x60):对‘luaL_loadbufferx(lua_State*, char const*, unsigned int, char const*, char const*)’未定义的引用
a.cpp:(.text+0x9b):对‘lua_pcallk(lua_State*, int, int, int, int, int (*)(lua_State*, int, int))’未定义的引用
a.cpp:(.text+0xe4):对‘lua_tolstring(lua_State*, int, unsigned int*)’未定义的引用
a.cpp:(.text+0x107):对‘lua_settop(lua_State*, int)’未定义的引用
a.cpp:(.text+0x140):对‘lua_close(lua_State*)’未定义的引用

解决方法:

原来是因为lua是C语言模块,用g++调用c语言的库需要在包含头文件时加上extern “C”,就能正常编译了,即修改为

 extern "C" {
 #include "lua.h"
 #include "lualib.h"
  #include "lauxlib.h"
}

原因:

因为Lua是用C语言写的,除非编译lua库时指定编译器强制以C++方式编译,否则在C++工程中应该这样包含lua头文件:

 extern "C" {
 #include "lua.h"
 #include "lualib.h"
  #include "lauxlib.h"
}

准备

安装lua编译器(没有动态链接库)

在Linux系统安装Lua,使用下面的命令下载并生成Lua程序:

$ wget http://www.lua.org/ftp/lua-5.2.3.tar.gz
$ tar zxf lua-5.2.3.tar.gz
$ cd lua-5.2.3
$ make linux test

在其它系统上安装Lua时,比如aix,ansi,bsd,generic,linux,mingw,posix,solaris,你需要将make linux test命令中的linux替换为相应的系统平台名称。

指向完成之后可以在src目录下生成liblua.a

错误

lua.c:67:31: 致命错误:readline/readline.h:没有那个文件或目录
 #include 
                               ^
编译中断。
make[2]: *** [lua.o] 错误 1
make[2]: 离开目录“/home/oceanstar/workspace/cpp/lua-5.2.3/src”
make[1]: *** [linux] 错误 2
make[1]: 离开目录“/home/oceanstar/workspace/cpp/lua-5.2.3/src”
make: *** [linux] 错误 2

解决:

yum install libtermcap-devel ncurses-devel libevent-devel readline-devel

注意

在linux下编译安装lua默认是不会生成liblua.so文件的,如果想要生成liblua.so文件需要修改makefile文件。 要么自己直接到这里去下载。生成方法可以参考这里
C/C++编程:Lua与C/C++的交互_第2张图片

环境搭建好后,所有C/C++代码需要引用lua.hpp或lua.h, lualib.h, lauxlib.h,运行需要连接liblua.so

C/C++调用Lua

文件目录结构

C/C++编程:Lua与C/C++的交互_第3张图片

lua测试代码

test.lua

name = "bob"
age= 20
mystr="hello lua"
mytable={name="tom",id=123456}

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

示例1:通过C语言读取Lua中的变量

main.cpp

#include 
extern "C" {
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
};
int main()
{
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);
    int retLoad = luaL_loadfile(L, "/home/oceanstar/CLionProjects/untitled1/src/test.lua");
    if (retLoad == 0)
    {
        printf("load file success retLoad:%d\n", retLoad);
    }
    if (retLoad || lua_pcall(L, 0, 0, 0))
    {
        printf("error %s\n", lua_tostring(L, -1));
        return -1;
    }

    lua_getglobal(L, "name"); //lua获取全局变量name的值并且返回到栈顶
    lua_getglobal(L, "age");  //lua获取全局变量age的值并且返回到栈顶,这个时候length对应的值将代替width的值成为新栈顶
    //注意读取顺序
    int age = lua_tointeger(L, -1); //栈顶
    const char *name = lua_tostring(L, -2);//次栈顶
    printf("name = %s\n", name);
    printf("age = %d\n", age);

    lua_close(L);
    return 0;
}

测试结果:
在这里插入图片描述
这里面需要注意的是,当出现多个lua_getglobal()函数的时候由上到下,依次压入栈,在使用lua_to*(L,index)函数读取栈顶元素的时候,如果每次压入栈的数据类型都不一样,那么在读取的是就要注意读取顺序。

当使用lua_tointegers()的是,返回值类型的long long

#define LUA_INTEGER      long long
typedef LUA_INTEGER      lua_Integer;
LUA_API lua_Integer      (lua_tointegerx) (lua_State *L, int idx, int *isnum);

当使用lua_tolstring()的时候,返回值类型是const char*

LUA_API const char *(lua_tolstring) (lua_State *L, int idx, size_t *len);

当使用lua_tonumber()的是,返回值类型是double

#define LUA_NUMBER	double
typedef LUA_NUMBER      lua_Number;
LUA_API lua_Number      (lua_tonumberx) (lua_State *L, int idx, int *isnum);

示例2:通过C语言调用Lua中的函数

main.cpp

#include 
extern "C" {
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
};
int main()
{
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);
    int retLoad = luaL_loadfile(L, "/home/oceanstar/CLionProjects/untitled1/src/test.lua");
    if (retLoad || lua_pcall(L, 0, 0, 0))
    {
        printf("error %s\n", lua_tostring(L, -1));
        return -1;
    }

    //调用函数,依次压入参数
    lua_getglobal(L, "add");
    lua_pushnumber(L, 10);
    lua_pushnumber(L, 20);
    //查看压入栈的元素
    for (int i=1;i<3;i++)
    {
        printf("number:%f\n",lua_tonumber(L, -i));
    }
    //lua_pcall(L,2,1,0):传入两个参数 期望得到一个返回值,0表示错误处理函数在栈中的索引值,压入结果前会弹出函数和参数
    int pcallRet = lua_pcall(L, 2, 1, 0); //lua_pcall将计算好的值压入栈顶,并返回状态值
    if (pcallRet != 0)
    {
        printf("error %s\n", lua_tostring(L, -1));
        return -1;
    }

    int val = lua_tonumber(L, -1); //在栈顶取出数据
    printf("val:%d\n", val);
    lua_pop(L, -1); //弹出栈顶
    //再次查看栈内元素,发现什么都没有,因为lua在返回函数计算值后会清空栈,只保留返回值
    for (int i=1;i<3;i++)
    {
        printf("number:%f\n",lua_tonumber(L, -i));
    }

    lua_close(L);
    return 0;
}

结果:
C/C++编程:Lua与C/C++的交互_第4张图片
由结果可以看出来,当调用完lua中的函数以后,会自动清空栈,只保留结果在栈顶。

注意:这个时候我们修改一下test.lua中的add函数:把2改为4

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

这时不进行编译,直接再运行一下./main,可以看到这个结果改变了从40变成了60,这是在我们没有进行重复编译的情况下直接产生的变化。

这就可以看出lua在C/C++语言中的嵌入特性,Lua中的函数就像是文本一样被读取,但是又确实是作为程序被执行。当我们的项目很大时,每次编译都需要十几分钟,这个时候如果合理的利用lua特性,仅仅是修改lua文件就可以避免这十几分钟的空白时间。

实例3:通过C语言调用Lua中的table

#include 
extern "C" {
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
};
int main()
{
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);
    int retLoad = luaL_loadfile(L, "/home/oceanstar/CLionProjects/untitled1/src/test.lua");
    if (retLoad || lua_pcall(L, 0, 0, 0))
    {
        printf("error %s\n", lua_tostring(L, -1));
        return -1;
    }

    printf("读取lua table中对应的值\n");
    //将全局变量mytable压入栈
    lua_getglobal(L, "mytable");
    //压入表中的key
    lua_pushstring(L, "name");

    //lua_gettable会在栈顶取出一个元素并且返回把查找到的值压入栈顶
    lua_gettable(L, 1);
    const char *name = lua_tostring(L, -1); //在栈顶取出数据
    printf("name:%s\n", name);

    lua_pushstring(L,"id");//压入id
    lua_gettable(L, 1);//在lua mytable表中取值返回到栈顶
    int id = lua_tonumber(L, -1); //在栈顶取出数据
    printf("id:%d\n", id);

    lua_close(L);
    return 0;
}

在这里插入图片描述

Lua调用C/C++

文章目录

  • 理论
    • lua中的栈
    • 常用的lua api
    • Lua中调用CPP函数
  • 准备
  • C/C++调用Lua
    • 示例1:通过C语言读取Lua中的变量
    • 示例2:通过C语言调用Lua中的函数
    • 实例3:通过C语言调用Lua中的table
  • Lua调用C/C++

你可能感兴趣的:(C++,lua,c语言,c++)