Lua与C++交互:函数

来源:互联网 发布:360软件管家字体模糊 编辑:程序博客网 时间:2024/05/17 07:00

Lua虚拟栈

Lua和C++交互,必须通过Lua虚拟栈,所以首先要理解Lua虚拟栈。
栈的特点是先进后出,在Lua中,Lua堆栈是一个struct,它的索引可以是正数也可以是负数,区别是:正数索引1永远表示栈底,负数索引-1永远表示栈顶,lua的栈是在lua_State的时候创建的。
这里写图片描述

  1. lua中, number, boolean, nil, light userdata四种类型的值是直接存在栈上元素里的, 和垃圾回收无关.

  2. lua中, string, table, closure, userdata, thread存在栈上元素里的只是指针, 他们都会在生命周期结束后被垃圾回收.

Lua栈的操作,Lua与C/C++是通过栈来通信,Lua提供了C API对栈进行操作

操作需要操作Lua栈,需要导入lua.h,lauxlib.h,lualib.h,luaconf.h四个头文件

#include <stdio.h>extern "C"{    #include <lua.h>    #include <lualib.h>    #include <lauxlib.h>}void main(){    lua_State *L = luaL_newstate();     //1.基本的库都加载进来    /* 官网文档提到不要直接调用 luaopen函数    //打开基础库    luaopen_base(L);    //打开表    luaopen_table(L);    //打开io    luaopen_io(L);    //打开字符串库    luaopen_string(L);    //打开数学库    luaopen_math(L);    */    lua_cpcall(L, luaopen_base, NULL);    lua_cpcall(L, luaopen_table, NULL);    lua_cpcall(L, luaopen_io, NULL);    lua_cpcall(L, luaopen_string, NULL);    lua_cpcall(L, luaopen_math, NULL);    //2.入栈操作      lua_pushstring(L, "Hello world");       lua_pushnumber(L,20);    //3.取值操作      if( lua_isstring(L,1)){             //判断是否可以转为string          printf("%s",lua_tostring(L,1));      }      if( lua_isnumber(L,2)){          printf("%s",lua_tonumber(L,1));       }    //关闭State    lua_close(L);    }

C++调用Lua

lua和c通信时有这样的约定: 所有的lua中的值由lua来管理, c++中产生的值lua不知道, 类似表达了这样一种意思: “如果你(c/c++)想要什么, 你告诉我(lua), 我来产生, 然后放到栈上, 你只能通过api来操作这个值, 我只管我的世界”, 这个很重要, 因为:
“如果你想要什么, 你告诉我, 我来产生”就可以保证, 凡是lua中的变量, lua要负责这些变量的生命周期和垃圾回收, 所以, 必须由lua来创建这些值(在创建时就加入了生命周期管理要用到的簿记信息)”然后放到栈上, 你只能通过api来操作这个值”, lua api给c提供了一套完备的操作界面, 这个就相当于约定的通信协议, 如果lua客户使用这个操作界面, 那么lua本身不会出现任何”意料之外”的错误.”我只管我的世界”这句话体现了lua和c/c++作为两个不同系统的分界, c/c++中的值, lua是不知道的, lua只负责它的世界

Lua代码:Test.lua

str = "Hello Lua"tab = {name = "ZhangSan",age = 22}function Add(a,b)    print(a + b);    return a + b;end

现在通过Test.cpp执行它

#include <stdio.h>extern "C"{    #include <lua.h>    #include <lualib.h>    #include <lauxlib.h>}void main(){    lua_State *L = luaL_newstate();     {        //直接执行Lua代码        luaL_dostring(L, "print('Hello Lua')");        //执行Lua文件代码        luaL_dofile(L, "Test.lua");        //读取变量        //查找到变量,并压入栈        lua_getglobal(L,"str");        string str = lua_tostring(L,-1);        //读取table        lua_getglobal(L,"tab");        //把 index[k] 的值压栈,从-1的位置获取name的值,并将它压栈        lua_getfield(L,-1,"name");        str = lua_tostring(L,-1);        //修改table        // 将需要设置的值设置到栈中          lua_pushstring(L, "我是一个大帅锅~");          // 将这个值设置到table中(此时tbl在栈的位置为2)          lua_setfield(L, 2, "name");        //创建一个新table        // 创建一个新的table,并压入栈          lua_newtable(L);          // 往table中设置值          lua_pushstring(L, "Give me a girl friend !"); //将值压入栈         //将值设置到table中,并将Give me a girl friend 出栈         lua_setfield(L, -2, "str");         //读取函数        lua_getglobal(L, "Add");        //第一个参数压栈        lua_pushinteger(L, 6);        //第二个参数压栈        lua_pushinteger(L, 5);        //调用Add函数,同时会对Add函数及两个参数进行出栈,并压入返回值        lua_call(L, 2, 1);//2:参数个数,1:返回值个数        int result = lua_tointeger(L, -1);//从栈中取返回值,也就是获取栈顶        lua_pop(L, 4);//清栈    }    //关闭State    lua_close(L);    }

重点:堆栈操作是基于栈顶的,就是说它只会去操作栈顶的值

Lua调用C++

在lua中注册自己的函数,函数必须遵循规范(可在lua.h中查看)如下:

typedef int (*lua_CFunction) (lua_State *L);

换句话说,所有的函数必须接收一个lua_State作为参数,同时返回一个整数值。因为这个函数使用Lua栈作为参数,所以它可以从栈里面读取任意数量和任意类型的参数。而这个函数的返回值则表示函数返回时有多少返回值被压入Lua栈。(因为Lua的函数是可以返回多个值的)

1.直接将模块写入Lua源码中,单个函数注册

我们在Cpp文件加入如下函数:

int GetTwoVar(lua_State * L){    // 向函数栈中压入2个值     lua_pushnumber(L, 10);    lua_pushstring(L, "hello world");    int a = lua_tonumber(L, 1);    int b = lua_tointeger(L, 2);    printf("A = %d,B = %d;", a, b);    return 2;}

在main函数里加入:

lua_register(L, "GetTwoVar", GetTwoVar);/* lua_register :做了两步,第一步将GetTwoVar压栈,                第二步:将栈元素链接表中元素,做成一对键值表    相等于:    lua_pushcfunction(L, getTwoVar); //将函数放入栈中    lua_setglobal(L, "getTwoVar");   //设置lua全局变量getTwoVar*/luaL_dostring(L,"print(GetTwoVar(111,999))");

一般我们不建议去修改别人的代码,更倾向于自己编写独立的C/C++模块,供Lua调用,下面来讲讲如何实现。

2.使用使用静态依赖的方式

  1. 新建一个空的win32控制台工程,记得在vc++目录中,把lua中的头文件和lib文件的目录包含进来,然后->链接器->附加依赖项->将lua51.lib和lua5.1.lib也包含进来。
  2. 在目录下新建一个avg.lua如下:
avg, sum = average(10, 20, 30, 40, 50)  print("The average is ", avg)  print("The sum is ", sum)

3.新建test.cpp如下:

#include <stdio.h>extern "C" {      #include "lua.h"      #include "lualib.h"      #include "lauxlib.h"  }  /* 指向Lua解释器的指针 */  lua_State* L;  static int average(lua_State *L)  {      /* 得到参数个数 */      int n = lua_gettop(L);      double sum = 0;      int i;      /* 循环求参数之和 */      for (i = 1; i <= n; i++)      {          /* 求和 */          sum += lua_tonumber(L, i);      }      /* 压入平均值 */      lua_pushnumber(L, sum / n);      /* 压入和 */      lua_pushnumber(L, sum);      /* 返回返回值的个数 */      return 2;  }  int main ( int argc, char *argv[] )  {      /* 初始化Lua */      L = lua_open();      /* 载入Lua基本库 */      luaL_openlibs(L);      /* 注册函数 */      lua_register(L, "average", average);      /* 运行脚本 */      luaL_dofile(L, "avg.lua");      /* 清除Lua */      lua_close(L);      /* 暂停 */      printf( "Press enter to exit…" );      getchar();      return 0;  }

3.使用dll动态链接的方式:批量注册,不加载到全局库里,推荐使用

我们先新建一个dll工程,工程名为mLualib。(因此最后导出的dll也为mLualib.dll)
然后编写我们的c++模块,以函数为例,我们先新建一个.h文件和.cpp文件

h文件如下:

#pragma once  extern "C" {      #include "lua.h"      #include "lualib.h"      #include "lauxlib.h"  }  #ifdef LUA_EXPORTS  #define LUA_API __declspec(dllexport)  #else  #define LUA_API __declspec(dllimport)  #endif  extern "C" LUA_API int luaopen_mLualib(lua_State *L);//定义导出函数

.cpp文件如下:

#include <stdio.h>  #include "mLualib.h"  static int averageFunc(lua_State *L)  {      int n = lua_gettop(L);      double sum = 0;      int i;      /* 循环求参数之和 */      for (i = 1; i <= n; i++)          sum += lua_tonumber(L, i);      lua_pushnumber(L, sum / n);     //压入平均值      lua_pushnumber(L, sum);         //压入和      return 2;                       //返回两个结果  }  static int sayHelloFunc(lua_State* L)  {      printf("hello world!");      return 0;  }  static const struct luaL_Reg myLib[] =   {      {"average", averageFunc},      {"sayHello", sayHelloFunc},      {NULL, NULL}       //数组中最后一对必须是{NULL, NULL},用来表示结束      };  int luaopen_mLualib(lua_State *L)  {      luaL_register(L, "ss", myLib);      return 1;       // 把myLib表压入了栈中,所以就需要返回1  }

然后编译它,然后新建一个lua文件,在lua中我们这样子来调用:(调用之前记得把dll文件复制到lua文件目录下)

require "mLualib"  local ave,sum = ss.average(1,2,3,4,5)//参数对应堆栈中的数据  print(ave,sum)  -- 3 15  ss.sayHello()   -- hello world!

至此都发生了什么呢?梳理一下:

1.我们编写了averageFunc求平均值和sayHelloFunc函数,

2.然后把函数封装myLib数组里面,类型必须是luaL_Reg

3.由luaopen_mLualib函数导出并在lua中注册这两个函数。

4.加载到全局库,这样会使全局库变乱,不推荐使用

#include <stdio.h>#include<string.h>extern "C"{    #include <lua.h>    #include <lualib.h>    #include <lauxlib.h>}#pragma comnent(lib,"lua5.1.lib");int GetTwoVar(lua_State * L){    // 向函数栈中压入2个值     lua_pushnumber(L, 10);    lua_pushstring(L, "hello world");    int a = lua_tonumber(L, 1);    int b = lua_tointeger(L, 2);    printf("A = %d,B = %d;", a, b);    return 2;}luaL_Reg funcs[] ={    {"GetTwoVar1",GetTwoVar},    { "GetTwoVar2",GetTwoVar },    { "GetTwoVar3",GetTwoVar },    { "GetTwoVar4",GetTwoVar },    { "GetTwoVar5",GetTwoVar },    {0,0},};void main(){    lua_State* L = luaL_newstate();    //加载基础库    lua_openlib(L);    //Lua调用C++函数    //函数要遵循规范(可在lua.h中查看)如下:    //typedef int (*lua_CFunction) (lua_State *L);    //所有的函数必须接收一个lua_State作为参数,    //  同时返回一个整数值。因为这个函数使用Lua栈作为参数,    //  所以它可以从栈里面读取任意数量和任意类型的参数。    //  而这个函数的返回值则表示函数返回时有多少返回值被压入Lua栈。    //  (因为Lua的函数是可以返回多个值的)    //Lua调用C++函数,批量注册到Lua    //:加载到全集库    //1:状态机,2:库名称,3:函数的地址数组,4:是否加到系统项上    luaL_openlib(L, "myLid", funcs, 0);    luaL_dostring(L, "myLid.GetTwoVar1(999,0)");    luaL_dostring(L, "myLid.GetTwoVar2(888,0)");    luaL_dostring(L, "myLid.GetTwoVar3(777,0)");    lua_close(L);}

需要注意的是:函数参数里的lua_State是私有的,每一个函数都有自己的栈。当一个C/C++函数把返回值压入Lua栈以后,该栈会自动被清空。

原创粉丝点击