Lua 是一个小巧的脚本语言,它本身就是作为嵌入脚本而设计的,在目前所有脚本引擎中,Lua的速度是最快的。而且它的解释器非常轻量,其解释器不过200k(不同版本可能略有差异)。

Lua项目包含许多技术点,花些时间研究可以有不少收获,学到很多东西。包括与宿主语言的交互、内存管理、虚拟机实现、协程、闭包、异常捕获机制等等,后续有时间慢慢研究下。

如题所示,本系列主要记录Lua封装相关笔记,主要是记录C++11的相关学习与实践。Lua相关原理并不懂,在笔记中也暂不提,后续有时间深入学习Lua时,再做相关笔记。

Lua与C/C++交互基础

Lua和C/C++语言通信的主要方法是通过Lua先进后出(FILO)的虚拟栈。在Lua中,Lua堆栈就是一个struct,堆栈索引的方式可是是正数也可以是负数,区别是:正数索引1永远表示栈底,负数索引-1永远表示栈顶。

Lua的使用,依赖于Lua的状态机lua_State,Lua的堆栈也存在于状态机中。这里的状态机,类似与Java的jvm,是解析、执行lua的基石。无论是Lua调用C/C++,还是C/C++调用Lua,都是A把数据压栈、B把数据从栈取出来,这里的数据可能是元数据也可能是一个地址。

在做Lua与C/C++交互时,方法、参数、返回值都需要压入堆栈。在后面会用例子来说明。

C/C++调用Lua

C/C++调用Lua相对来说比较简单,需要注意的是,当使用C++时,在引入Lua头文件时候,需要使用#include "lua.hpp",在lua.hpp内容如下,使用了extern c来告知编译器,以C Linkage方式编译,也就是抑制C++的name mangling机制。否则会编译出错。

extern "C" {#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}

然后我们需要有一个Lua文件:

function helloAdd(num1, num2)return (num1 + num2)
end

在C/C++中调用Lua的步骤如下:

void testCCallLua(){int ret;//第一步:创建一个Lua的状态机lua_State * l = luaL_newstate();//第二步:打开需要使用的库。虽然例子没用到,这个还是全部打开。还有另外的方法打开指定的库luaopen_xxx(l)luaL_openlibs(l);//第三步:加载(执行)指定的lua文件,lua没有main函数。函数、table、全局变量什么的都到栈中了,函数外面的程序直接就执行了ret = luaL_dofile(l, "../res/test1.lua");std::cout<<"doFile : "<< ret<< std::endl;//第四步:获取指定的lua函数,这一步会把第二个参数压入堆栈中ret = lua_getglobal(l, "helloAdd");std::cout<<"getFunction : "<< ret<< std::endl;//第五步:把函数需要的参数压入到栈中lua_pushnumber(l, 10);lua_pushnumber(l, 5);//第六步:调用函数前面压入到栈中的函数,第二个参数为被调函数的参数个数,第三个参数为返回个数,因为lua是支持多个返回值的lua_call(l, 2, 1);//第六步:调用函数后,结果被放在栈顶,按照类型去取结果。多个返回值的时候,需要注意索引值,取完结果后弹出数据。结果也可以每取一个调用lua_pop弹出被取出的结果double iResult = lua_tonumber(l, -1);std::cout<<"result:" << iResult << std::endl;lua_pop(l,1);//最后一步: 使用完后,要注意关闭释放状态机lua_close(l);l = nullptr;
}

Lua调用C

Lua调用C可以通过注册库、然后在lua中加载库的方式来调用,也可以通过直接函数压栈的方式来调用。在这里记录的为函数压栈的方式。

这里,我们先准备好要被调用的C函数。

// 这是原来的C函数
double cFuncAdd(double a, double b){return a+b;
}// C函数被调用,需要符合Lua的规则,所以需要做一个转调
// 返回值表示函数返回值的个数
int luaBindCFuncAdd(lua_State * l){double a = luaL_checknumber(l, 1);double b = luaL_checknumber(l, 2);lua_pushnumber(l, cFuncAdd(a, b));return 1;
}

C函数被调用,需要符合lua的规则。前面提到过,C和Lua的互调都是通过栈来完成的,Lua解释器在解释Lua中调用的函数时,也是通过函数名、函数参数等去和堆栈做交互的。所以在上面luaBindCFuncAdd中,实际执行也是从堆栈中依次取出两个参数,然后调用C函数执行,并把结果压入栈中,然后lua从栈中得到结果。

Lua只是嵌入脚本,它的执行依赖宿主程序,所以我们还是需要写C代码来执行lua,并且上面我们只是准备好了C函数,但是这个C函数并没有通过Lua状态机和lua建立联系。

void testLuaCallC(){std::cout << "start test Lua Call C --------------------- " << std::endl;//前面步骤一样,创建状态机,打开lua库lua_State * l = luaL_newstate();luaL_openlibs(l);//把C函数压入栈中,第二个参数只接受输入参数是lua_State,输出位int值的函数。lua_pushcfunction(l,luaBindCFuncAdd);//对应C调用lua方法的那个getglobal,这里是把栈顶的函数取个名字lua_setglobal(l,"cFuncAdd");//然后执行lua程序,这里直接写了一串lua代码,输出函数执行结果int ret = luaL_dostring(l,"print('cFuncAdd ret :', cFuncAdd(98,9))");std::cout<<"doFile:" << ret << std::endl;lua_close(l);
}

C和Lua的相互调用这样其实就比较好理解了,把C的函数放入堆栈中被Lua调用,还是lua中的函数被lua调用,至少从表现上来说,并没有区别,可能都是执行前把方法压入了堆栈,执行时,根据名字,找到对于的函数放到栈顶,然后压入参数,执行函数,再从栈顶得到返回的结果。

Lua调用C++

这个就相对麻烦点了,上面的Lua调用C的时候,我们知道lua_pushcfunction只能传入固定格式的C函数。要让Lua可以调用C++,而且按照我们在C++中使用类的方式来进行调用,我们就需要对C++代码做更多的处理去满足lua的要求了。实际上,巧用Lua的userdata可以满足我们的各种需求。

在这里的示例中,实际上是通过userdata+metatable来实现C++到Lua的映射,把C++类映射为Lua中的table。
其中metatable在Lua中,允许我们改变table的行为。在Lua中,每个行为关联了对应的元方法。常见的元方法有:

__index //通过table获取table的属性及方法时会调用此方法
__gc    //table被回收时,会调用此方法
__newindex //对表更新,和__index类似,__index是访问旧值,对不存在的索引增加值时会调用此方法
//以下这些都对应着表的运算符,可以看做是用于运算符的重载。
__add、__sub、__mul、__div、__mod、__unm、__concat、__eq、__lt、__le
__call //在 Lua 调用一个值时调用
__tostring //用于修改表的输出行为,相当于java中的tostring方法重载

给一个table设置元表(metatable)后,在对table执行某个操作时,就会按照元表的定义来执行。比如,一个table设置了元表,元表中实现了__index元方法,则table.xxx和table:xxx都会先执行__index方法,通过__index决定应该做什么。

我们在后面就是用Lua的这个特性,来实现对C++的调用。

首先,写下我们最终期望的Lua代码:

-- 我们创建了一个类,然后去使用它的方法,并且也能访问它的属性
operate = OperateCpp()
print("OperateCpp:multiply ret : ", operate:multiply(5.0,12.0))
print("OperateCpp.errorCode is :", operate.errorCode)

准备C++的类,和转调用的C函数:


class OperateCpp{private:double x{};double y{};int type{};
public:int errorCode = -1;OperateCpp() = default;~OperateCpp(){std::cout<<"OperateCpp destroy"<<std::endl;}double multiply(double x, double y){return x * y;}
};//构造函数的转调函数
static int LuaCreateOperateCpp(lua_State * l){//Lua状态机是不知道C++类这个东西的,但是Lua中有userdata来支持扩展//所以,在这里,我们先指定大小,获取一块内存,并放进堆栈中,作为一个userdata。这块内存,宿主程序是可以直接使用的auto ** pData = (OperateCpp**)lua_newuserdata(l, sizeof(OperateCpp*));//我们把userdata中的内容,赋值为一个C++类实例的指针*pData = new OperateCpp();//然后我们去获取一个名为OperateCpp元表,然后放到栈顶。这个名字可以随意,但是要确保这个元表是存在的。//在关联的时候,我们会去创建一个这样的元表,确保在这里可以获取到。luaL_getmetatable(l, "OperateCpp");//把元表和指定位置的userdata关联起来,这里是-2,就是上面new出来的,-1被上面指定的元表占据了lua_setmetatable(l, -2);return 1;
}//销毁对象
static int LuaDestroyOperateCpp(lua_State* L){// 释放对象delete *(OperateCpp**)lua_topointer(L, 1);return 0;
}//函数的转调
static int LuaFuncMultiply(lua_State * l){auto * oc = *(OperateCpp **)lua_topointer(l, 1);auto x = lua_tonumber(l,2);auto y = lua_tonumber(l,3);auto ret = oc->multiply(x,y);lua_pushnumber(l,ret);return 1;
}//注意这个函数在后面的用途,这个会被指定给元表的__index方法
static int LuaCallIndex(lua_State * l){auto * oc = *(OperateCpp **)lua_topointer(l, 1);auto filed = lua_tostring(l,2);if(strcmp(filed,"errorCode") == 0){lua_pushnumber(l, oc->errorCode);}else if(strcmp(filed, "multiply") == 0){lua_pushcfunction(l, LuaFuncMultiply);}return 1;
}

然后我们需要建立C++和Lua的联系,解释也直接在代码注释中给出:


void testLuaCallCpp(){std::cout << "start test Lua Call Cpp --------------------- " << std::endl;lua_State * l = luaL_newstate();luaL_openlibs(l);//和前面一样,我们用OperateCpp来命名OperateCpp对象的构造函数的转调函数,命名位OperateCpp,在Lua中调用就是OperateCpp()了//创建函数被调用时,每次实际就是创建了一个table,然后关联一个元表lua_pushcfunction(l,LuaCreateOperateCpp);lua_setglobal(l,"OperateCpp");//创建元表,提供给每次创建对象时候用,这个名字可以随意,只要在创建函数中取元表的时候要和这个对应//这里的元表可以看到和上面的构建方法是一样的,这个无所谓,一样不一样都行luaL_newmetatable(l, "OperateCpp");//指定元表的__gc方法,关联这个元表的table,在被释放时就会调用指定的方法lua_pushstring(l,"__gc");lua_pushcfunction(l,LuaDestroyOperateCpp);//这里相当于把堆栈的指针给移回到metatable上,以便于继续设置其他元方法lua_settable(l, -3);//设置元表__index的元方法,关联这个元表的table,每次获取内部属性或方法时就会调用指定的方法lua_pushstring(l, "__index");lua_pushcfunction(l,LuaCallIndex);lua_settable(l,-3);std::string content = loadString("../res/test2.lua");int ret = luaL_dostring(l,content.c_str());std::cout<<"doFile:" << ret << std::endl;lua_close(l);
}

至此,Lua和C/C++基本的相互调用就完成了,但是这样使用起来难免会感觉比较麻烦,我只是想做个简单的交互就要做这么多事情,无意是痛苦的,所以后面我们就要开始对这写复杂的调用做封装,让Lua和C++的交互变的更简单一些。

其他

笔记相关的代码在Github上,代码会不断变动,有需要的可以直接看对应的提交。此博客仅作为个人学习笔记及有兴趣的朋友参考使用,虚心接受建议与指正,不接受吐槽和批评,引用设计思想或代码希望注明出处,欢迎Fork和Star。wLuaBind代码地址


欢迎转载,转载请保留文章出处。湖广午王的博客[http://blog.csdn.net/junzia/article/details/95001209]


Lua封装C++实践(一)——Lua和C/C++的基本交互相关推荐

  1. Lua封装C++实践(三)——Lua注册C++构造函数

    一个std::tuple<int,float,std::string>这样的结构,如何传递给int call(int,float ,std::string)这样的函数作为参数?如何根据函数 ...

  2. Lua封装C++实践(二)—— C++调用Lua函数的封装

    在上篇博客中,记录了Lua与C/C++的基本交互,但是如果按照那样来使用的话,实在太麻烦了,所以我们开始进行封装.本篇博客主要记录C++调用Lua函数的封装. 封装目标 C++调用Lua,复杂的地方主 ...

  3. [zz]为 lua 封装 C 对象的生存期管理问题

    转载自:http://blog.codingnow.com/2009/03/lua_c_wrapper.html 把 C 里的对象封装到 lua 中,方便 lua 程序调用,是很常见的一项工作. 里面 ...

  4. lua源代码分析01:lua源代码结构分析

    目录 一.什么是lua 二.lua源代码结构 三.阅读lua源代码顺序 一.什么是lua 1.lua是用C编写的脚本语言,可以在web.游戏.物联网等场景下使用,源代码共1万多行:可以独立编程,可以嵌 ...

  5. android studio lua插件,android Studio 配置LUA 开发环境

    android Studio 配置 LUA开发环境 关于Android LUA资料 引诉大牛的原话: Android 调用 Lua /Lua 调用 Android 代码 在Android项目中使用Lu ...

  6. 脚本语言lua笔记(5)c++调用lua

    首先搭建环境,使用vs2010的c++开发工具,lua源码包,可以去官方下载最新源码包,我采用的是lua-5.1.5的版本.好了,开始配环境. 第一步: 下载源码包后,解压lua-5.1.5源码包到硬 ...

  7. linux lua socket编程,CentOs 安装lua,luasocket

    一.centos安装Lua 3)个人在这里选择使用5.1版本的 *下载 wget http://www.lua.org/ftp/lua-5.1.5.tar.gz --2013-10-14 16:23: ...

  8. 【Lua进阶系列】实例lua调用capi

                             [Lua进阶系列]实例lua调用capi     大家好,我是Lampard~~     欢迎来到Lua进阶系列的博客     首先祝大家2021新年 ...

  9. Redis学习笔记 - Lua脚本(2) - Lua脚本的实现

    参考:<<Redis设计与实现>> 注:这本书是基于Redis3.0版本写的,和后面的版本有点差异 Redis中Lua脚本相关命令介绍以及简单使用,参考博客:https://b ...

最新文章

  1. Kubernetes1.5源码分析(二) apiServer之资源注册
  2. 成功解决RuntimeWarning: invalid value encountered in double_scalars
  3. greenplum(一)
  4. python图像识别车票_是程序员就用Python查12306的票
  5. python语言的类型是_Python到底是强类型语言,还是弱类型语言?
  6. Android--OkHttp理解系列(一)
  7. debug 和release 的区别
  8. think php 3.2.3 环境,ThinkPHP 3.2.3 入口文件配置
  9. iis php日志查看工具,教你如何查看IIS日志
  10. 勒索软件Locky最新传播载体分析——中文版Office危在旦夕
  11. ubuntu 双击打不开软件或者创建的快捷方式
  12. 实用干货秘籍!最经典的10个Pandas数据查询案例,收藏!
  13. 贾俊平统计学思维导图- 第十三章 时间序列分析和预测
  14. Jack Platts:Polkadot 在 Staking 上的设计
  15. 第四篇:UE4视角切换节点,Possess和Set View Target With Blend的区别
  16. Vue父组件与子组件传递事件/调用事件
  17. 爆款制作获1200w播放,B站UP主+品牌如何迈入2023
  18. 笔记本固态盘数据丢失怎么办?笔记本固态盘怎么恢复数据
  19. APP功能测试包含哪些方面?最全详细总结(教程)清晰易懂
  20. 数据包络分析(超效率-SBM模型)附python代码

热门文章

  1. const和let有什么区别
  2. 宽带换了新的账号怎么连接服务器地址,换宽带后路由器怎么设置
  3. 第四章 快速傅里叶变换之三 按频率抽选的基-2FFT算法
  4. solidworks中弹簧与圆柱体如何配合?五步教会你
  5. AI产教融合新标杆!百度与“华东六校”共建人工智能微专业
  6. 【地球科学】DEM数据简要总结及ALOS PALSAR12.5m数据获取和对比显示
  7. 关于UI切图与开发 px和dp
  8. 福布斯上面的前50区块链
  9. 硬件电路开发中发光二极管(LED)常用知识
  10. 【校准教程】数字万用表校准操作视频