Lua和C++交互详细总结

来源:互联网 发布:万网域名申请流程 编辑:程序博客网 时间:2024/06/06 02:03

转自 http://www.cnblogs.com/sevenyuan/p/4511808.html

一、Lua堆栈

要理解Lua和C++交互,首先要理解Lua堆栈。

简单来说,Lua和C/C++语言通信的主要方法是一个无处不在的虚拟栈。栈的特点是先进后出。

在Lua中,Lua堆栈就是一个struct,堆栈索引的方式可是是正数也可以是负数,区别是:正数索引1永远表示栈底,负数索引-1永远表示栈顶。如图:

 lua的栈类似于以下的定义, 它是在创建lua_State的时候创建的:

             TValue stack[max_stack_len]  // 欲知内情可以查 lstate.c 的stack_init函数

    存入栈的数据类型包括数值, 字符串, 指针, talbe, 闭包等, 下面是一个栈的例子:

          

   执行下面的代码就可以让你的lua栈上呈现图中的情况

    lua_pushcclosure(L, func, 0) // 创建并压入一个闭包

    lua_createtable(L, 0, 0)        // 新建并压入一个表

    lua_pushnumber(L, 343)      // 压入一个数字

    lua_pushstring(L, “mystr”)   // 压入一个字符串

 

    这里要说明的是, 你压入的类型有数值, 字符串, 表和闭包[在c中看来是不同类型的值], 但是最后都是统一用TValue这种数据结构来保存的:), 下面用图简单的说明一下这种数据结构:

    

 

    TValue结构对应于lua中的所有数据类型, 是一个{值, 类型} 结构, 这就lua中动态类型的实现, 它把值和类型绑在一起, 用tt记录value的类型, value是一个联合结构, 由Value定义, 可以看到这个联合有四个域, 先说明简单的

        p -- 可以存一个指针, 实际上是lua中的light userdata结构

        n -- 所有的数值存在这里, 不过是int , 还是float

        b -- Boolean值存在这里, 注意, lua_pushinteger不是存在这里, 而是存在n中, b只存布尔

        gc -- 其他诸如table, thread, closure, string需要内存管理垃圾回收的类型都存在这里

        gc是一个指针, 它可以指向的类型由联合体GCObject定义, 从图中可以看出, 有string, userdata, closure, table, proto, upvalue, thread

    从下面的图可以的得出如下结论:

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

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

 

二、堆栈的操作

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

我们先来看一个最简单的例子:

复制代码
#include <iostream>  #include <string.h>  using namespace std;     extern "C"  {      #include "lua.h"      #include "lauxlib.h"      #include "lualib.h"  }  void main()  {      //1.创建一个state      lua_State *L = luaL_newstate();             //2.入栈操作      lua_pushstring(L, "I am so cool~");       lua_pushnumber(L,20);         //3.取值操作      if( lua_isstring(L,1)){             //判断是否可以转为string          cout<<lua_tostring(L,1)<<endl;  //转为string并返回      }      if( lua_isnumber(L,2)){          cout<<lua_tonumber(L,2)<<endl;      }         //4.关闭state      lua_close(L);      return ;  }
复制代码

可以简单理解为luaL_newstate返回一个指向堆栈的指针,其它看注释应该能懂了吧。

 

其他一些栈操作:

复制代码
int   lua_gettop (lua_State *L);            //返回栈顶索引(即栈长度)  void  lua_settop (lua_State *L, int idx);   //                void  lua_pushvalue (lua_State *L, int idx);//将idx索引上的值的副本压入栈顶  void  lua_remove (lua_State *L, int idx);   //移除idx索引上的值  void  lua_insert (lua_State *L, int idx);   //弹出栈顶元素,并插入索引idx位置  void  lua_replace (lua_State *L, int idx);  //弹出栈顶元素,并替换索引idx位置的值
复制代码

ua_settop将栈顶设置为一个指定的位置,即修改栈中元素的数量。如果值比原栈顶高,则高的部分nil补足,如果值比原栈低,则原栈高出的部分舍弃。所以可以用lua_settop(0)来清空栈

 

三、C++调用Lua

我们经常可以使用Lua文件来作配置文件。类似ini,xml等文件配置信息。现在我们来使用C++来读取Lua文件中的变量,table,函数。

 

 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只负责它的世界

 

现在有这样一个hello.lua 文件:

str = "I am so cool"  tbl = {name = "shun", id = 20114442}  function add(a,b)      return a + b  end

我们写一个test.cpp来读取它:

复制代码
#include <iostream>  #include <string.h>  using namespace std;     extern "C"  {      #include "lua.h"      #include "lauxlib.h"      #include "lualib.h"  }  void main()  {      //1.创建Lua状态      lua_State *L = luaL_newstate();      if (L == NULL)      {          return ;      }         //2.加载Lua文件      int bRet = luaL_loadfile(L,"hello.lua");      if(bRet)      {          cout<<"load file error"<<endl;          return ;      }         //3.运行Lua文件      bRet = lua_pcall(L,0,0,0);      if(bRet)      {          cout<<"pcall error"<<endl;          return ;      }         //4.读取变量      lua_getglobal(L,"str");      string str = lua_tostring(L,-1);      cout<<"str = "<<str.c_str()<<endl;        //str = I am so cool~         //5.读取table      lua_getglobal(L,"tbl");       lua_getfield(L,-1,"name");      str = lua_tostring(L,-1);      cout<<"tbl:name = "<<str.c_str()<<endl; //tbl:name = shun         //6.读取函数      lua_getglobal(L, "add");        // 获取函数,压入栈中      lua_pushnumber(L, 10);          // 压入第一个参数      lua_pushnumber(L, 20);          // 压入第二个参数      int iRet= lua_pcall(L, 2, 1, 0);// 调用函数,调用完成以后,会将返回值压入栈中,2表示参数个数,1表示返回结果个数。      if (iRet)                       // 调用出错      {          const char *pErrorMsg = lua_tostring(L, -1);          cout << pErrorMsg << endl;          lua_close(L);          return ;      }      if (lua_isnumber(L, -1))        //取值输出      {          double fValue = lua_tonumber(L, -1);          cout << "Result is " << fValue << endl;      }         //至此,栈中的情况是:      //=================== 栈顶 ===================       //  索引  类型      值      //   4   int:      30       //   3   string:   shun       //   2   table:     tbl      //   1   string:    I am so cool~      //=================== 栈底 ===================          //7.关闭state      lua_close(L);      return ;  }
复制代码

知道怎么读取后,我们来看下如何修改上面代码中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 !"); //将值压入栈  lua_setfield(L, -2, "str"); //将值设置到table中,并将Give me a girl friend 出栈

需要注意的是:堆栈操作是基于栈顶的,就是说它只会去操作栈顶的值。

 

举个比较简单的例子,函数调用流程是先将函数入栈,参数入栈,然后用lua_pcall调用函数,此时栈顶为参数,栈底为函数,所以栈过程大致会是:参数出栈->保存参数->参数出栈->保存参数->函数出栈->调用函数->返回结果入栈。

类似的还有lua_setfield,设置一个表的值,肯定要先将值出栈,保存,再去找表的位置。

 

再不理解可看如下例子:

复制代码
lua_getglobal(L, "add");        // 获取函数,压入栈中  lua_pushnumber(L, 10);          // 压入第一个参数  lua_pushnumber(L, 20);          // 压入第二个参数  int iRet= lua_pcall(L, 2, 1, 0);// 将2个参数出栈,函数出栈,压入函数返回结果  lua_pushstring(L, "我是一个大帅锅~");  //   lua_setfield(L, 2, "name");             // 会将"我是一个大帅锅~"出栈
复制代码

另外补充一下:

lua_getglobal(L,"var")会执行两步操作:1.将var放入栈中,2.由Lua去寻找变量var的值,并将变量var的值返回栈顶(替换var)。

lua_getfield(L,-1,"name")的作用等价于 lua_pushstring(L,"name") + lua_gettable(L,-2)

 

lua value 和 c value的对应关系

            c         lua         nil          无   {value=0, tt = t_nil}      boolean      int  非0, 0   {value=非0/0, tt = t_boolean}      number      int/float等   1.5   {value=1.5, tt = t_number}   lightuserdata   void*, int*, 各种*  point   {value=point, tt = t_lightuserdata}      string         char  str[]   {value=gco, tt = t_string}   gco=TString obj      table           无   {value=gco, tt = t_table}  gco=Table obj      userdata           无   {value=gco, tt = t_udata} gco=Udata obj      closure           无   {value=gco, tt = t_function} gco=Closure obj

 

可以看出来, lua中提供的一些类型和c中是对应的, 也提供一些c中没有的类型. 其中有一些药特别的说明一下:

        nil值, c中没有对应, 但是可以通过lua_pushnil向lua中压入一个nil值

        注意: lua_push*族函数都有"创建一个类型的值并压入"的语义, 因为lua中所有的变量都是lua中创建并保存的, 对于那些和c中有对应关系的lua类型, lua会通过api传来的附加参数, 创建出对应类型的lua变量放在栈顶, 对于c中没有对应类型的lua类型, lua直接创建出对应变量放在栈顶.

       例如:    lua_pushstring(L, “string”) lua根据"string"创建一个 TString obj, 绑定到新分配的栈顶元素上

                  lua_pushcclosure(L,func, 0) lua根据func创建一个 Closure obj, 绑定到新分配的栈顶元素上

                  lua_pushnumber(L,5) lua直接修改新分配的栈顶元素, 将5赋值到对应的域

                  lua_createtable(L,0, 0)lua创建一个Tabke obj, 绑定到新分配的栈顶元素上

 

       总之, 这是一个 c value –> lua value的流向, 不管是想把一个简单的5放入lua的世界, 还是创建一个table, 都会导致

                  1. 栈顶新分配元素    2. 绑定或赋值

                还是为了重复一句话, 一个c value入栈就是进入了lua的世界, lua会生成一个对应的结构并管理起来, 从此就不再依赖这个c value

        lua value –> c value时, 是通过 lua_to* 族api实现, 很简单, 取出对应的c中的域的值就行了, 只能转化那些c中有对应值的lua value, 比如table就不能to c value, 所以api中夜没有提供 lua_totable这样的接口.

 

四、Lua调用C++

我们分三个方法实现它。

 

方法一:直接将模块写入Lua源码中

在Lua中调用C/C++,我们可以将函数写lua.c中,然后重新编译Lua文件。

编译好后是这样子的:(如图)

然后我们可以在lua.c中加入我们自己的函数。函数要遵循规范(可在lua.h中查看)如下:

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

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

 

然后我们在lua.c中加入如下函数:

复制代码
// This is my function  static int getTwoVar(lua_State *L)  {      // 向函数栈中压入2个值      lua_pushnumber(L, 10);      lua_pushstring(L,"hello");         return 2;  }   在pmain函数中,luaL_openlibs函数后加入以下代码://注册函数  lua_pushcfunction(L, getTwoVar); //将函数放入栈中  lua_setglobal(L, "getTwoVar");   //设置lua全局变量getTwoVar
复制代码

通过查找lua.h

/#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n)))

我们发现之前的注册函数可以这样子写:

lua_register(L,"getTwoVar",getTwoVar);

运行,结果如图:

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

 

方法二:使用静态依赖的方式

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;  }
复制代码

执行一下,我们可以得到结果:

大概顺序就是:我们在C++中写一个模块函数,将函数注册到Lua解释器中,然后由C++去执行我们的Lua文件,然后在Lua中调用刚刚注册的函数。

 

看上去很别扭啊有木有。接下来介绍一下dll调用方式。

 

方法三:使用dll动态链接的方式

我们先新建一个dll工程,工程名为mLualib。(因此最后导出的dll也为mLualib.dll)

 

然后编写我们的c++模块,以函数为例,我们先新建一个.h文件和.cpp文件。

 

h文件如下:(如果你不是很能明白头文件的内容,点击这里:http://blog.csdn.net/shun_fzll/article/details/39078971。)

复制代码
#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中注册这两个函数。

 

那么为什么要这样子写呢?实际上当我们在Lua中:

require "mLualib"

这样子写的时候,Lua会这么干:

local path = "mLualib.dll"    local f = package.loadlib(path,"luaopen_mLualib")   -- 返回luaopen_mLualib函数  f()                                                 -- 执行

所以当我们在编写一个这样的模块的时候,编写luaopen_xxx导出函数的时候,xxx最好是和项目名一样(因为项目名和dll一样)。

 

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

 

五、总结 

  • Lua和C++是通过一个虚拟栈来交互的。

  • C++调用Lua实际上是:由C++先把数据放入栈中,由Lua去栈中取数据,然后返回数据对应的值到栈顶,再由栈顶返回C++。

  • Lua调C++也一样:先编写自己的C模块,然后注册函数到Lua解释器中,然后由Lua去调用这个模块的函数。

 

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 3d渲大图慢怎么办 23岁就眼皮下垂怎么办 手被猫咪抓破了怎么办 有幻想症的人怎么办 做事不专心老走神怎么办 小朋友做作业老发呆走神怎么办? 小朋友总是上课走神发呆怎么办 21岁精神心急了怎么办 一直有人阻止我做事怎么办 安装了渲染su找不到怎么办 su室内渲染很暗怎么办 su未响应未保存怎么办 电脑工作栏没了怎么办 ps工作栏没了怎么办 草图大师文件太大打不开怎么办 su模型保存后不见了怎么办 墨汁溅到衣服上怎么办 黑裤子溅上白色颜料怎么办 解码器上墙后图像放大了怎么办 宝宝把墙画了怎么办 孩子画画勾线笔过敏怎么办 微信语音撤回了怎么办? 小天才电话手表充不上电怎么办 儿童电话手表定位不准怎么办 2岁宝宝不让刷牙怎么办 宝宝两岁蛀牙多还不刷牙怎么办 宝宝有蛀牙不肯刷牙怎么办 宝宝牙疼怎么办4岁 3岁宝宝龋齿牙疼怎么办 2岁宝宝不肯刷牙怎么办 3岁宝宝不爱刷牙怎么办 2岁宝宝不刷牙怎么办 二岁宝宝牙不好怎么办 小孩一刷牙就吐怎么办 孩子一刷牙就吐怎么办 两岁宝宝闹人怎么办 3岁宝宝不愿意刷牙怎么办 孩子牙没掉长出新牙来了怎么办 大孩子不洗澡怎么办啊 2岁宝宝不爱洗澡怎么办 手指画颜料变干怎么办