找回密码
 立即注册
查看: 331|回复: 0

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

[复制链接]
发表于 2022-4-15 14:04 | 显示全部楼层 |阅读模式
文章目录


      一、编译及使用流程
        1)编译lua静态库2)编译tolua++静态库3)编译tolua++.exe4)根据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_pushusertype2)lua_pushnil3)lua_setglobal4)lua_gettop5)lua_pushcclosure6)lua_getglobal7)lua_push*8)lua_pcall9)lua_to*10)lua_settop11)lua_load(注意,lua里面调用lua函数的时候,会省去lua_,也就是说直接调用load)12)lua_type(L, -1) 判断栈顶元素的类型,返回值为类型13)lua_gettable14)lua_next15)lua_settable16)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)
  1. #defineFALSE0#defineTRUE1enum{
  2. POINT =100,
  3. LINE,
  4. POLYGON
  5. }
  6. Object*createObejct(int type);voiddrawObject(Object* obj,double red,double green,double blue);intisSelected(Object* obj);
复制代码
就会自动创建一个绑定上面代码到lua的C文件。因此,在lua代码里, 我们可以访问C代码。举个例子:
    pkg文件
  1. $pfile "XX.pkg"    //$pfile是包含对应的pkg文件
  2. $hfile  "XXX.h"     //$hfile是包含对应的.h头文件
  3. $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
    调用流程展示
  1. ...
  2. myLine =createObject(LINE)--...ifisSelected(myLine)== TRUE thendrawObject(myLine,1.0,0.0,0.0);elsedrawObject(myLine,1.0,1.0,1.0);end...
复制代码
(2)C++文件举例

    C++文件
  1. #defineFALSE0#defineTRUE1classShape{voiddraw(void);voiddraw(double red,double green,double blue);intisSelected(void);};classLine:publicShape{Line(double x1,double y1,double x2,double y2);~Line(void);};
复制代码
如果tolua输入加载该文件,就会自动生成一个C++文件,例如tolua,从而为我们提供lua层访问C++层所需要的对应的代码。因此,以下的的lua代码是有效的:
  1. ...
  2. myLine = Line:new(0,0,1,1)--new表示创建一个类的新对象,这个对象是myLine
  3. ...if myLine:isSelected()== TRUE then                --对象::对应函数表示调用对应函数
  4. myLine:draw(1.0,0.0,0.0)else
  5. myLine:draw()--基类的函数也是直接调用就行
  6. end
  7. ...
  8. myLine:delete()--delete就是析构函数
  9. ...
复制代码
二、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)代码示例
  1. //main.cpp#include<iostream>extern"C"{#include"lualib.h"#include"lauxlib.h"}usingnamespace std;intmain(){inttolua_mylib_open(lua_State*);
  2.     lua_State* state =luaL_newstate();luaL_openlibs(state);if(luaL_dostring(state,"print([[hello world]])")!=0){
  3.         cout <<"excute lua file failed!"<< endl;}lua_close(state);system("pause");return0;}
复制代码
如果正确打印出 “hello world”,则说明 lua 环境没有问题,否则就检查一下头文件和库文件是否正确引入了
5)生成函数的API介绍

    原始.c和.h文件
  1. //.h文件//mylib.hclassTest{public:Test(int a,int b);~Test();voidsayHello();intadd();intgetA();private:int a;int b;};//.cpp文件//mylib.cpp#include"mylib.h"#include<iostream>Test::Test(int a,int b){this->a = a;this->b = b;}Test::~Test(){}voidTest::sayHello(){
  2.     std::cout <<"hello world"<< std::endl;}intTest::add(){returnthis->a +this->b;}intTest::getA(){returnthis->a;}
复制代码
编译指令:tolua -n mylib -o tolua.cpp mylib.pkg

    pkg文件
  1. $#include "mylib.h"classTest{Test(int,int);~Test();voidsayHello();intadd();intgetA();};
复制代码
    生成代码
  1. //类名是Test//垃圾回收staticinttolua_collect_Test(lua_State* tolua_S){}//注册类名staticvoidtolua_reg_types(lua_State* tolua_S){}//对应构造函数staticinttolua_mylib_Test_new00(lua_State* tolua_S){}//对应析构函数staticinttolua_mylib_Test_delete00(lua_State* tolua_S){}//对应sayHello函数staticinttolua_mylib_Test_sayHello00(lua_State* tolua_S){}//对应add函数staticinttolua_mylib_Test_add00(lua_State* tolua_S){}//getAstaticinttolua_mylib_Test_getA00(lua_State* tolua_S){}//luaopen_pkgname用于打开库文件
  2. LUALIB_API intluaopen_mylib(lua_State* tolua_S){}//luaopen_pkgname_open于打开 tolua,然后lua就可以使用C++的函数了
  3. TOLUA_API inttolua_mylib_open(lua_State* tolua_S){}
复制代码
三、lua调用C++

1)代码

    lua文件
  1. local test = Test:new(1,2)
  2. test:sayHello()print("a = ".. test:getA())print("a + b = ".. test:add())
复制代码
    cpp文件
  1. #include<iostream>extern"C"{#include"lualib.h"#include"lauxlib.h"}//Test类的源文件mylib.h#include"mylib.h"usingnamespace std;intmain(){
  2.     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文件{
  3.         cout <<"excute lua file failed!"<< endl;lua_close(state);return1;}lua_close(state);system("pause");return0;}
复制代码
2)执行结果



四、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文件
  1. //lib.h#pragmaonce#include"lua.h"extern"C"{__declspec(dllexport)intluaopen_test(lua_State* L);}
复制代码
    .cpp文件
  1. //lib.cpp#include"mylib.h"extern"C"{#include"lib.h"#include"lualib.h"#include"lauxlib.h"}staticintexport_tolua_test(lua_State* L){inttolua_mylib_open(lua_State* L);tolua_mylib_open(L);return1;}staticconst luaL_Reg toluaTest[]={{"export_tolua_test",export_tolua_test },{NULL,NULL}};extern"C"{__declspec(dllexport)intluaopen_test(lua_State* L){luaL_newlibtable(L, toluaTest);luaL_setfuncs(L, toluaTest,0);return1;}}
复制代码
编译之后得到一个 lib.dll 文件,在 lua 代码中加载这个动态链接库文件,得到一个模块,这个模块里面有 export_tolua_test 这个函数,调用这个函数就会执行 tolua_mylib_open(L);,从而打开 tolua,则在 mylib 中导出的所有 c++ 东西都可以在 lua 中使用了
  1. -- main.lua
  2. -- 加载 c++ 模块
  3. local test =require("test")-- 调用 c++ 模块注册的函数,这个函数会打开 tolua
  4. test.export_tolua_test()-- 调用 tolua 导出的类、函数等
  5. local t = Test:new(10,20)
  6. t:sayHello()print(t:getA())print(t:add())
复制代码
使用 lua 解释器运行该代码,看到下面的执行结果


五、遇到的tolua和lua的API



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对象
    使用举例
  1. //函数定义voidtolua_pushsertype(lua_State* L,void* value ,constchar* type)//pRole是一个CRole的指针,指向一个CRole的对象tolua_pushusertype(pScript->get_lua_state(),pRole,"CRole");
复制代码
2)lua_pushnil

    作用
    压入nil类型数据
    函数
  1. voidlua_pushnil(lua_State *L);
复制代码
    测试
  1. lua_pushnil(L);dump_stack(L);//输出----{ dump_stack ----
  2. nil   
  3. ---- dump_stack }----
复制代码
3)lua_setglobal

    函数定义
  1. voidlua_setglobal((lua_State *L,constchar* name);
复制代码
    函数作用:
    Pops a value from the stack and sets it as the new value of global name.
    (把从堆栈中弹出一个值并将其设置为 global 的新值name)然后给函数设置一个在lua中的调用名称,(调用完成后,会将栈顶元素弹出,将字符串name设置为全局变量,可以在lua里面调用)使用举例
  1. 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

    函数原型
  1. voidlua_pushcclosure(lua_State *L, lua_CFunction fn,int n);//可以封装为#definelua_pushcfunction(L,f)lua_pushcclosure(L,(f),0)
复制代码
    作用
    调用以创建 C 函数并将其压入堆栈,参数n告诉应与该函数关联的值的数量。
6)lua_getglobal

    函数定义
  1. voidlua_getglobal(lua_State *L,constchar*name);
复制代码
    作用
    从全局变量中获取一个name参数描述的变量,放到栈顶
    使用举例
    name可以是一个lua函数(function)的名字,然后用lua_isfunction确保-1这个栈顶是位置的元素是函数
    效果
    将获得的变量,放置到栈顶
7)lua_push*

    函数
  1. 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

    原型
  1. 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

    原型
  1. voidlua_settop(lua_State *L,int index);
复制代码
    作用
    如果新传入的index比原来的栈顶大,那么超出的元素被填入nil. 可以传入0,那么栈将会被清空
    对栈的操作
    会修改栈的大小
11)lua_load(注意,lua里面调用lua函数的时候,会省去lua_,也就是说直接调用load)

    函数定义
  1. intlua_load(lua_State *L,
  2.               lua_Reader reader,void*data,constchar*chunkname,constchar*mode);
复制代码
    lua里面调用直接load就可以调用
  1. local f ,msg=load(cmd_func);//cmd_func是个字符串,msg是个返回信息,可以返回错误信息
  2. f();//执行lua函数
复制代码
    作用
    加载一段 Lua 代码块,但不运行它。 如果没有错误, lua_load 把一个编译好的代码块作为一个 Lua 函数压到栈顶。 否则,压入错误消息。
    返回值
    lua_load 的返回值可以是:
  1. LUA_OK: 没有错误;
  2. LUA_ERRSYNTAX: 在预编译时碰到语法错误;
  3. LUA_ERRMEM: 内存分配错误;
  4. LUA_ERRGCMM: 在运行 __gc 元方法时出错了。 (这个错误和代码块加载过程无关,它是由垃圾收集器引发的。)
复制代码
    参数说明
    1)使用一个用户提供的 reader 函数来读取代码块(参见 lua_Reader )。 data 参数会被传入 reader 函数。
    源码
  1. LUA_API intlua_load(lua_State *L, lua_Reader reader,void*data,constchar*chunkname,constchar*mode){
  2.   ZIO z;int status;lua_lock(L);if(!chunkname) chunkname ="?";luaZ_init(L,&z, reader, data);
  3.   status =luaD_protectedparser(L,&z, chunkname, mode);if(status == LUA_OK){/* no errors? */
  4.     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) 判断栈顶元素的类型,返回值为类型

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

    获取单个元素使用流程
  1. 1)保证栈顶是个table
  2. 2)压栈一个键值对的key值,比如
  3. lua_pushnumber(L, 1); --或字符串
  4. 3)table一开始是在栈顶,即-1处的,但上面的语句压入了一个值,栈顶变-2了。
  5. 4)lua_gettable的作用就是以栈顶的值作为key来访问-2位置上的table。
  6. lua_gettable(L, -2);
  7. 5)这时table中的第1个元素的值就放到栈顶了
  8. 6)补充:对于字符串索引
  9. 可以用lua_getfield(Lua_state,index,key)来直接获取,
  10. 如:lua_getfield(stack, -1, "loaded");等价于 lua_pushstring(L,"loaded") lua_gettable(L,-2);
复制代码
    遍历table中的所有元素
    1)如果table是一个以连续的整形作为key的table, 可以用下面方法:
  1. 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是任意值呢?可以用下面的方法:
  1. 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

    函数表达式
  1. voidlua_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 。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2025-5-17 09:28 , Processed in 0.245437 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表