lua与c++交互

来源:互联网 发布:mac docker vs pd 编辑:程序博客网 时间:2024/06/04 18:41

lua和C++交互详细总结

前言

原理:Lua和C语言通信的主要方法是一个无所不在的虚拟栈。几乎所有的API调用都会操作这个栈上的值。所有的数据交换,无论是Lua到C语言或者是C语言到Lua都是通过这个栈来完成。

可以把Lua看成C的一个库,lua解释器(即Lua库里的那个应用程序)使得lua可以编写程序,它依靠Lua库来实现主要功能。它处理与用户的交互(真正运行Lua代码就是一种交互)


一、lua堆栈   

1、lua堆栈的用处?
简单来说,lua与c/c++语言通信的主要方法是依靠这个无处不在的虚拟栈(先进后出)

虚拟栈的形状:

记忆诀窍:正数表示第几个放进去的,负数表示是倒数第几个放进去的

2、lua堆栈的结构?

Lua堆栈就是一个struct


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

    关于不同类型如何在这个堆栈中存储(即不同的类型在堆栈中储存方式)总结如下:

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

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

二、堆栈的操作

因为Lua与C/C++是通过栈来通信,Lua提供了C API对栈进行操作(栈的操作只能在c代码里进行)

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

<1.要获取lua中的一个值时,只要调用一个api函数,lua就会将指定的值压入栈中。

<2.要将一个值传给lua时,需要先将这个值压入栈,然后调用api,lua就会将获取该值并将其从栈中弹出。

#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 ;  }

上边的代码只是c语言这边对栈进行的操作(c语言入栈,c语言取值,没有涉及到与lua的交互,只是体现了单方面操作虚拟栈)

下边总结一些常用的栈操作的api函数

lua_pushcclosure(L, func, 0) //创建并压入一个闭包lua_createtable(L, 0, 0) //新建并压入一个表lua_pushnumber(L, 30) //压入一个数字30lua_pushstring(L, "zfx") //压入一个字符串...
其他一些栈操作

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位置的值
lua_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只负责它的世界

-----------------------------------------------示例分界线(begin)---------------------------------------------------------------------

lua文件(hell0.lua):

str = "I am so cool"   -- 一个字符串tbl = {name = "shun", id = 20114442}  -- 一个table  function add(a,b)   -- 一个函数    return a + b  end

然后就是c++文件(text.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");  //获取lua中全局名字为str的变量(实际上是一个字符串),压入栈中    string str = lua_tostring(L,-1);  //把堆栈中-1(现在就是str)转换为字符串,取值    cout<<"str = "<<str.c_str()<<endl;        //str = I am so cool~         //5.读取table      lua_getglobal(L,"tbl");   //获取table,压入栈中    lua_getfield(L,-1,"name");  //栈顶(现在是table)取"name"的值,压入栈中    str = lua_tostring(L,-1);  //栈顶(现在是name的字符串)    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 ;  }

上边需要注意的是函数的调用过程



-----------------------------------------------示例分界线(end)---------------------------------------------------------------------

四、Lua调用C++

可以用四个方法实现:

1.直接将模块写入lua源码中(不介意)

在Lua中调用C/C++,我们可以将函数写lua.c中,然后重新编译Lua文件。(具体做法可以参照http://www.cnblogs.com/sevenyuan/p/4511808.html)

2.使用dll动态链接的方式

我们新建一个dll(zfx.dll)工程 --> 然后编写c++模块(并且编译) --> 新建一个Lua文件(关键是lua文件的写法)如下:

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

方式是:

<1.我们编写几个函数在C++中

<2.然后函数封装在C++的一个数组中,类型必须是luaL_Reg

<3.C++模块中的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栈以后,该栈会自动被清空。


(具体做法可以参照http://www.cnblogs.com/sevenyuan/p/4511808.html

3.使用静态依赖的方式

<1.新建一个空的win32控制台工程,记得在vc++目录中,把Lua中的头文件和lib文件的目录包含进来,然后 连接器->附加依赖性->将lua51.lib和Lua5.1.lib也包含进来。

<2.在目录下新建一个avg.lua如下:

avg, sum = average(10, 20, 30, 40, 50)    -- average函数lua中没有,等待调用c模块的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);   //第二个参数是lua中函数使用的名字,第三个参数是c模块函数名    /* 运行脚本 */      luaL_dofile(L, "avg.lua");      /* 清除Lua */      lua_close(L);         /* 暂停 */      printf( "Press enter to exit…" );      getchar();      return 0;  }
执行一下,我们可以得到结果:

The average is 30The sum is 150Press enter to exit...
执行顺序是:c++中写一个模块函数 --> 将函数注册到Lua解释器中 --> C++去执行LUA文件 --> lua调用刚刚注册的函数

注册函数机制记忆法:因为lua和c交互,c是主lua是客,所以c调用lua可以直接调用,而lua调用c需要注册先。



五、总结

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

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

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

待续...




0 0