lua学习03:tolua的编译和使用:C/C++调用lua、lua调用C++、lua调用tolua、常用tolua和lua的API介绍

文章目录

    • 一、编译及使用流程
      • 1)编译lua静态库
      • 2)编译tolua++静态库
      • 3)编译tolua++.exe
      • 4)根据C/C++文件编写好对应的pkg文件,然后根据一创命令生成tolua
        • (1)C文件举例
        • (2)C++文件举例
    • 二、C++调用lua
      • 1)流程
      • 2)注意点:
      • 3)要包含的库和头文件
      • 4)代码示例
      • 5)生成函数的API介绍
    • 三、lua调用C++
      • 1)代码
      • 2)执行结果
    • 四、lua调用tolua
    • 五、遇到的tolua和lua的API
      • 1)tolua_pushusertype
      • 2)lua_pushnil
      • 3)lua_setglobal
      • 4)lua_gettop
      • 5)lua_pushcclosure
      • 6)lua_getglobal
      • 7)lua_push*
      • 8)lua_pcall
      • 9)lua_to*
      • 10)lua_settop
      • 11)lua_load(注意,lua里面调用lua函数的时候,会省去lua_,也就是说直接调用load)
      • 12)lua_type(L, -1) 判断栈顶元素的类型,返回值为类型
      • 13)lua_gettable
      • 14)lua_next
      • 15)lua_settable
      • 16)lua_rawlen

一、编译及使用流程

1)编译lua静态库

必须先下载lua 1.5,下载好后,把/src目录的除开lua.c,luac.c的文件全部放到一个新建的静态链接库的工程项目里,然后编译出一个静态链接库lua.lib。

2)编译tolua++静态库

去tolua++官网下载一个tolua++的包,解压出来,再次新建一个静态链接库的项目,把tolua++的/src/lib里的.h .c的文件拷贝到静态链接库的项目里,然后编译出一个tolua++.lib 的库。

3)编译tolua++.exe

再次新建一个控制台程序的项目,把编译好的lua静态库和tolua++的静态库放在项目里附加,然后把tolua++的/src/bin/里的tolua.c,luabind.h,luabind.c文件放在项目里,然后把tolua++的/include里的tolua++.h和lua1.5的目录里的/src的所有头文件放到项目里,然后编译生成tolua++.exe

4)根据C/C++文件编写好对应的pkg文件,然后根据一创命令生成tolua

(1)C文件举例
  • C文件(test1.c)
#define FALSE 0
#define TRUE 1

enum { 
 POINT = 100, 
 LINE, 
 POLYGON
}
Object* createObejct (int type);
void drawObject (Object* obj, double red, double green, double blue);
int isSelected (Object* obj);

就会自动创建一个绑定上面代码到lua的C文件。因此,在lua代码里, 我们可以访问C代码。举个例子:

  • pkg文件
$pfile "XX.pkg"    //$pfile是包含对应的pkg文件
$hfile  "XXX.h"     //$hfile是包含对应的.h头文件
$lfile  "XXX.lua"    //包含对应的.lua文件
  • 生成命令 (tolua -h显示当前可接受的选项)
    1)要解析一个名为test.pkg生成一个名为tolua_test.c的绑定代码,我们需要输入:
    tolua -o test.c test.pkg
    2)或生成一个不同的名字给pkg
    tolua -n pkgname -o myfile.c myfile.pkg

  • 调用流程展示

...
myLine = createObject(LINE)				--
...
if isSelected(myLine) == TRUE then
  drawObject(myLine, 1.0, 0.0, 0.0);
else
  drawObject(myLine, 1.0, 1.0, 1.0);
end
...
(2)C++文件举例
  • C++文件
#define FALSE 0
#define TRUE 1
class Shape
{
  void draw (void);
  void draw (double red, double green, double blue);
  int isSelected (void);
};
class Line : public Shape
{
 Line (double x1, double y1, double x2, double y2);
 ~Line (void);
};

如果tolua输入加载该文件,就会自动生成一个C++文件,例如tolua,从而为我们提供lua层访问C++层所需要的对应的代码。因此,以下的的lua代码是有效的:

...
myLine = Line:new (0,0,1,1)				--new表示创建一个类的新对象,这个对象是myLine
...
if myLine:isSelected() == TRUE then		--对象::对应函数表示调用对应函数
 myLine:draw(1.0,0.0,0.0)
else
 myLine:draw()							--基类的函数也是直接调用就行
end
...
myLine:delete()							--delete就是析构函数
...

二、C++调用lua

1)流程

(1)package还应当被明确初始化。为了从C/C++代码中初始化package,我们需要声明和调用初始化函数。初始化函数被定义为:

int tolua_pkgname_open (lua_State*); //这就是C++的函数,pkgname填写对应pkg的名字

(2)如果我们使用的是C++,我们可以选择自动初始化

tolua -a -n pkgname -o myfile.c myfile.pkg
不使用自动初始化是(下面的命令):
tolua -n pkgname -o tolua_myfile.c myfile.pkg //生成tolua_myfile.c文件

2)注意点:

在这种情况下,初始化函数会被自动调用。然而,如果我们计划使用多个Lua State,自动初始化就行不通了,因为静态变量初始化的顺序在C++里没有定义。

  • 补充
    tolua生成的绑定代码使用了一系列tolua库里面的函数。因此,这个库同样需要被链接到应用程序中。tolua.h也是有必须要编译生成的代码。tolua生成的绑定代码使用了一系列tolua库里面的函数。因此,这个库同样需要被链接到应用程序中。tolua.h也是有必须要编译生成的代码。

3)要包含的库和头文件

库: lua.lib 和 tolua.lib
头文件:lua.h、lualib.h、lauxlib.h、luaconf.h

4)代码示例

//main.cpp
#include 
extern "C"
{
#include "lualib.h"
#include "lauxlib.h"
}

using namespace std;

int main()
{
    int tolua_mylib_open(lua_State*);
    lua_State* state = luaL_newstate();
    luaL_openlibs(state);

    if (luaL_dostring(state, "print([[hello world]])") != 0)
    {
        cout << "excute lua file failed!" << endl;
    }
    lua_close(state);

    system("pause");
    return 0;
}

如果正确打印出 “hello world”,则说明 lua 环境没有问题,否则就检查一下头文件和库文件是否正确引入了

5)生成函数的API介绍

  • 原始.c和.h文件
//.h文件
//mylib.h
class Test
{
public:
    Test(int a, int b);
    ~Test();
    void sayHello();
    int add();
    int getA();

private:
    int a;
    int b;
};


//.cpp文件
//mylib.cpp
#include "mylib.h"
#include 

Test::Test(int a, int b)
{
    this->a = a;
    this->b = b;
}

Test::~Test()
{
}

void Test::sayHello()
{
    std::cout << "hello world" << std::endl;
}

int Test::add()
{
    return this->a + this->b;
}

int Test::getA()
{
    return this->a;
}

编译指令:tolua -n mylib -o tolua.cpp mylib.pkg

  • pkg文件
$#include "mylib.h"
class Test
{
    Test(int, int);
    ~Test();
    void sayHello();
    int add();  
    int getA();
};
  • 生成代码
//类名是Test

//垃圾回收
static int tolua_collect_Test (lua_State* tolua_S){}    

//注册类名
static void tolua_reg_types (lua_State* tolua_S){}

//对应构造函数
static int tolua_mylib_Test_new00(lua_State* tolua_S){}

//对应析构函数
static int tolua_mylib_Test_delete00(lua_State* tolua_S){}

//对应sayHello函数
static int tolua_mylib_Test_sayHello00(lua_State* tolua_S){}

//对应add函数
static int tolua_mylib_Test_add00(lua_State* tolua_S){}

//getA
static int tolua_mylib_Test_getA00(lua_State* tolua_S){}

//luaopen_pkgname用于打开库文件
LUALIB_API int luaopen_mylib (lua_State* tolua_S){}

//luaopen_pkgname_open于打开 tolua,然后lua就可以使用C++的函数了
TOLUA_API int tolua_mylib_open (lua_State* tolua_S){}

三、lua调用C++

1)代码

  • lua文件
local test = Test:new(1, 2)
test:sayHello()
print("a = " .. test:getA())
print("a + b = " .. test:add())
  • cpp文件
#include 
extern "C"
{
#include "lualib.h"
#include "lauxlib.h"
}
//Test类的源文件mylib.h
#include "mylib.h"

using namespace std;

int main()
{
    lua_State* state = luaL_newstate();	//创建lua虚拟机
    luaL_openlibs(state);				//打开lua的库
    tolua_mylib_open(state);			//打开tolua的入口,让lua可以调用mylib的C++类接口


    if (luaL_dofile(state, "scripts/test.lua") != 0)//加载test.lua文件
    {
        cout << "excute lua file failed!" << endl;
        lua_close(state);
        return 1;
    }
    lua_close(state);

    system("pause");
    return 0;
}

2)执行结果

lua学习03:tolua的编译和使用:C/C++调用lua、lua调用C++、lua调用tolua、常用tolua和lua的API介绍_第1张图片

四、lua调用tolua

这种方式其实和在 c++ 程序中使用 tolua 没什么区别,只是我们测试的项目是 c++ 项目还是 lua 项目而已。要在纯 lua 程序中使用 tolua,首先得导出一个 c++ 模块。新建一个 c++ 动态链接库项目,同样地将 lua 和 tolua 的头文件和库文件包含进来,还有 mylib.h、mylib.cpp 和 tolua.cpp 这三个文件也添加进项目来。

导出 c++ 模块,在模块内注册一个函数,在这个函数里打开 tolua,然后 tolua 绑定的所有 c++ 函数就可以在 lua 中使用了。我们创建一个 lib.h 和 lib.cpp 文件,用于导出 c++ 模块

  • .h文件
//lib.h
#pragma once
#include "lua.h"
extern "C"
{
    __declspec(dllexport) int luaopen_test(lua_State* L);
}
  • .cpp文件
//lib.cpp
#include "mylib.h"
extern "C"
{
#include "lib.h"
#include "lualib.h"
#include "lauxlib.h"
}

static int export_tolua_test(lua_State* L)
{
    int tolua_mylib_open(lua_State* L);
    tolua_mylib_open(L);
    return 1;
}
static const luaL_Reg toluaTest[] =
{
    { "export_tolua_test",export_tolua_test },
    { NULL,NULL }
};

extern "C"
{
    __declspec(dllexport) int luaopen_test(lua_State* L)
    {
        luaL_newlibtable(L, toluaTest);
        luaL_setfuncs(L, toluaTest, 0);
        return 1;
    }
}

编译之后得到一个 lib.dll 文件,在 lua 代码中加载这个动态链接库文件,得到一个模块,这个模块里面有 export_tolua_test 这个函数,调用这个函数就会执行 tolua_mylib_open(L);,从而打开 tolua,则在 mylib 中导出的所有 c++ 东西都可以在 lua 中使用了

-- main.lua
-- 加载 c++ 模块
local test = require("test")
-- 调用 c++ 模块注册的函数,这个函数会打开 tolua
test.export_tolua_test()

-- 调用 tolua 导出的类、函数等
local t = Test:new(10, 20)
t:sayHello()
print(t:getA())
print(t:add())

使用 lua 解释器运行该代码,看到下面的执行结果

在这里插入图片描述

五、遇到的tolua和lua的API

lua学习03:tolua的编译和使用:C/C++调用lua、lua调用C++、lua调用tolua、常用tolua和lua的API介绍_第2张图片

1)tolua_pushusertype

  • 函数作用
    1)传入 c++ 对象的 tolua++ 函数是 tolua_pushusertype。一般情况下,第一次使用这个函数将一个 c++ 对象 push 到 lua 堆栈上时,才会新建 userdata。tolua++ 会以 c++ 对象地址为键,userdata 为值,将键值对存储在 tolua_ubox 表里。下次推入同样的 c++ 对象时,从这个表里取出 userdata 推入堆栈即可
    2)将c++对象指针作为key, 类型作为value存储于tolua_ubox表(当然这个表里还有userdata)。若key不存在直接插入即可;若存在则根据继承关系判断是否满足条件,若满足则不做任何处理直接返回此userdata对象,若不满足则修订value类型。

  • 目的
    构造一个给lua用的userdata对象

  • 使用举例

//函数定义
void tolua_pushsertype(lua_State* L,void* value , const char* type)

//pRole是一个CRole的指针,指向一个CRole的对象
tolua_pushusertype(pScript->get_lua_state(),pRole,"CRole");	

2)lua_pushnil

  • 作用
    压入nil类型数据

  • 函数

void lua_pushnil(lua_State *L);
  • 测试
lua_pushnil(L);
dump_stack(L);

//输出
---- { dump_stack ----
nil    
---- dump_stack } ----

3)lua_setglobal

  • 函数定义
void lua_setglobal( (lua_State *L, const char* name);
  • 函数作用:
    Pops a value from the stack and sets it as the new value of global name.
    (把从堆栈中弹出一个值并将其设置为 global 的新值name)然后给函数设置一个在lua中的调用名称,(调用完成后,会将栈顶元素弹出,将字符串name设置为全局变量,可以在lua里面调用)
  • 使用举例
lua_setglobal(pScript->get_lua_state(),"role");

4)lua_gettop

  • 作用
    Returns the index of the top element in the stack.
    Because indices start at 1,
    this result is equal to the number of elements in the stack
    (and so 0 means an empty stack).

返回栈顶元素的索引,因为索引从1开始,所以这个结果也等于堆栈中元素的数量(0表示空的堆栈)

5)lua_pushcclosure

  • 函数原型
void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n);


//可以封装为
#define lua_pushcfunction(L,f)  lua_pushcclosure(L,(f),0)
  • 作用
    调用以创建 C 函数并将其压入堆栈,参数n告诉应与该函数关联的值的数量。

6)lua_getglobal

  • 函数定义
void lua_getglobal (lua_State *L, const char *name);
  • 作用
    从全局变量中获取一个name参数描述的变量,放到栈顶

  • 使用举例
    name可以是一个lua函数(function)的名字,然后用lua_isfunction确保-1这个栈顶是位置的元素是函数

  • 效果
    将获得的变量,放置到栈顶

7)lua_push*

  • 函数
  lua_pushnil(lua_State *L, xxx)
  lua_pushstring(lua_State *L, xxx)
  lua_pushnumber(lua_State *L, xxx)
  lua_pushinteger(lua_State *L, xxx)
  • 作用
    放置xxx元素到栈顶
  • 效果
    放置一个元素到栈顶

8)lua_pcall

  • 原型
  lua_pcall(lua_State *L, int nargs, int nresults, int errfunc);
  • 参数说明
    nargs指明了参数的个数
    nresults指明了返回结果的个数
    errfunc指明了发生错误的处理函数

  • 注意
    (注:调用lua_pcall之前,我们应该先:放置一个函数到栈中(可用:lua_getglobal),然后压入要传递的参数(可用:lua_push*))

  • 对栈的操作
    调用lua_pcall之后,它会首先将栈中的:函数,参数全部弹出 ; 然后:将结果一次压入栈中。

9)lua_to*

  • 作用:
    将index指明位置上的元素转换为对应类型返回

  • 对栈的操作
    不会改变栈的大小,内容.

10)lua_settop

  • 原型
  void lua_settop (lua_State *L, int index);
  • 作用
    如果新传入的index比原来的栈顶大,那么超出的元素被填入nil. 可以传入0,那么栈将会被清空

  • 对栈的操作
    会修改栈的大小

11)lua_load(注意,lua里面调用lua函数的时候,会省去lua_,也就是说直接调用load)

  • 函数定义
int lua_load (lua_State *L,
              lua_Reader reader,
              void *data,
              const char *chunkname,
              const char *mode);
  • lua里面调用直接load就可以调用
local f ,msg= load(cmd_func); //cmd_func是个字符串,msg是个返回信息,可以返回错误信息
f();                      //执行lua函数 
  • 作用
    加载一段 Lua 代码块,但不运行它。 如果没有错误, lua_load 把一个编译好的代码块作为一个 Lua 函数压到栈顶。 否则,压入错误消息。

  • 返回值
    lua_load 的返回值可以是:

LUA_OK: 没有错误;
LUA_ERRSYNTAX: 在预编译时碰到语法错误;
LUA_ERRMEM: 内存分配错误;
LUA_ERRGCMM: 在运行 __gc 元方法时出错了。 (这个错误和代码块加载过程无关,它是由垃圾收集器引发的。)
  • 参数说明
    1)使用一个用户提供的 reader 函数来读取代码块(参见 lua_Reader )。 data 参数会被传入 reader 函数。

  • 源码

LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data,
                      const char *chunkname, const char *mode) {
  ZIO z;
  int status;
  lua_lock(L);
  if (!chunkname) chunkname = "?";
  luaZ_init(L, &z, reader, data);
  status = luaD_protectedparser(L, &z, chunkname, mode);
  if (status == LUA_OK) {  /* no errors? */
    LClosure *f = clLvalue(s2v(L->top - 1));  /* get newly created function */
    if (f->nupvalues >= 1) {  /* does it have an upvalue? */
      /* get global table from registry */
      const TValue *gt = getGtable(L);
      /* set global table as 1st upvalue of 'f' (may be LUA_ENV) */
      setobj(L, f->upvals[0]->v, gt);
      luaC_barrier(L, f->upvals[0], gt);
    }
  }
  lua_unlock(L);
  return status;
}

12)lua_type(L, -1) 判断栈顶元素的类型,返回值为类型

  • 类型枚举(名字:枚举:含义)
LUA_TNIL 0  空,代表什么也没有,可以与C的NULL类比,但它不是空指针.
LUA_TBOOLEAN 1 只有"true"和"false"两个值.
LUA_TLITHTUSERDATA  2 用户(非脚本用户)定义的C数据结构.脚本用户只能使用它,不能定义
LUA_TNUMBER 3 内部以double表示.
LUA_TSTRING 4 总是以零结尾,但可以包含任意字符(包括零),因此并不等价于C字符串,而是其超集.
LUA_TTABLE 5 异构的Hash表.Lua的关键概念之一.
LUA_TFUNCTION 6 Lua的关键概念之一.不简单等同于C的函数或函数指针.
LUA_TUSERDATA 7 用户(非脚本用户)定义的C数据结构.脚本用户只能使用它,不能定义.
LUA_TTHREAD 8  Lua协作线程(coroutine),与一般操作系统的抢占式线程不一样.
LUA_NUMTAGS 9 

13)lua_gettable

  • 获取单个元素使用流程
1)保证栈顶是个table
2)压栈一个键值对的key值,比如
lua_pushnumber(L, 1); --或字符串
3)table一开始是在栈顶,即-1处的,但上面的语句压入了一个值,栈顶变-2了。
4)lua_gettable的作用就是以栈顶的值作为key来访问-2位置上的table。
lua_gettable(L, -2);
5)这时table中的第1个元素的值就放到栈顶了
6)补充:对于字符串索引
可以用lua_getfield(Lua_state,index,key)来直接获取,
如:lua_getfield(stack, -1, "loaded");等价于 lua_pushstring(L,"loaded") lua_gettable(L,-2);
  • 遍历table中的所有元素
    1)如果table是一个以连续的整形作为key的table, 可以用下面方法:
int size = lua_objlen(L,-1);//相关于#table  
for(int i = 1; i <= size; i++)  
{  
lua_pushnumber(L, i);  
lua_gettable(L, -2);  
//这时table[i]的值在栈顶了  
lua_pop(L, 1);//把栈顶的值移出栈,保证栈顶是table以便遍历。  
};

2)如果table中的key是任意值呢?可以用下面的方法:

lua_pushnill(L);  
while(lua_next(L, -2))  
{  
//这时值在-1(栈顶)处,key在-2处。  
lua_pop(L, 1);//把栈顶的值移出栈,让key成为栈顶以便继续遍历  
}

14)lua_next

  • 作用原理
    以栈顶元素为key,先判断上一个key的值(这个值放在栈顶,如果是nil,则表示当前取出的是table中第一个元素的值),然后得到当前的key和value。这时先把栈顶出栈,将新key进栈,后将value进栈。这样栈顶就是table中第一个遍历到的元素的值。用完这个值后,我们要把这个值出栈,让新key在栈顶以便继续遍历。当根据上一个key值算不出下一个key值时(其实这时候key的是多少并不重要,只要不为nil就行,因为为nil会返回table的第一个元素),lua_next返回0,结束循环。

15)lua_settable

  • 函数表达式
void lua_settable (lua_State *L, int index);
  • 函数作用
    lua_settable 会把栈顶作为value,栈顶的下一个作为key设置到index指向的table,最后把这两个弹出弹出栈,这时候settable完成

16)lua_rawlen

  • 函数
    size_t lua_rawlen (lua_State *L, int index);
  • 函数作用
    返回给定索引处值的固有“长度”: 对于字符串,它指字符串的长度; 对于表;它指不触发元方法的情况下取长度操作(’#’)应得到的值; 对于用户数据,它指为该用户数据分配的内存块的大小; 对于其它值,它为 0 。

你可能感兴趣的:(lua,lua,开发语言,cocos2d)