本篇在讲什么 C/C++和Lua的相互调用 本篇适合什么 适合初学Lua的小白 适合需要C/C++和lua结合开发的人 本篇需要什么 对Lua语法有简单认知 对C/C++语法有简单认知 依赖Lua5.1的环境 依赖VS 2017编辑器 本篇的特色 具有全流程的图文教学 重实践,轻理论,快速上手 提供全流程的源码内容 |
![]() ★提高阅读体验★ 👉 ♠ 一级标题 👈👉 ♥ 二级标题 👈👉 ♣ 三级标题 👈👉 ♦ 四级标题 👈 |
目录
♠ 为什么C/C++和Lua可以相互调用
Lua运行的解释器是用Lua标准库实现的独立解释器,所以Lua是一种嵌入型语言,可以被当做库来拓展其他应用
Lua可以通过固定的C API
实现和C语言的交互
♠ 为什么要C/C++和Lua相互调用
Lua作为脚本语言,不需要预先编译,加上其语法简单扩展性强的特点,尝尝作为胶水
去完成其他应用的拓展,例如常见使用Lua作为热更的解决方案
Lua存在自身的局限性,通过调用C/C++编写的外部库,可以实现Lua不方便实现的功能,或实现效率更高的方式
♠ Lua的C API
C API是一个函数、常量和类型组成的集合,有了它,C语言代码就能与Lua语言交互
C API包括读写Lua全局变量的函数、调用Lua函数的函数、运行Lua代码段的函数,以及注册C函数(以便于其后可被Lua代码调用)的函数等
通过调用C API, C代码几乎可以做到Lua代码能够做的所有事情
♥ 加载标准库
如果你本地安装有Lua环境,可以很简单的在项目内加载Lua的标准库,下面文章可以了解如何在vs中引入lua的标准库
VIsual Studio内引用Lua解释器,编译Lua源码,执行Lua脚本
♠ C++和Lua的相互调用
我们尝试性的使用C++去加载一个lua文件,并一点点去解释和理解其中每个步骤的作用
♥ 数据交换
首先我们需要知道的是Lua和C之间的通信通过虚拟栈(stack)
当我们想要从Lua获取一个值(例如一个全局变量)时,会经过以下过程
- C获取Lua的值
需要调用Lua将指定的值压入栈中,C再去栈中获取
- Lua获取C的值
首先通过C将这个值压入栈,然后调用Lua将其从栈中弹出即可
如上图所示,交换数据都需要经过这个中间栈,数据交换的过程,就是入栈出栈的过程
栈的索引可以通过正数和负数表示,正数栈底索引1,负数栈顶为-1
C API提供了完善的接口让我们去操作对栈内数据的处理
♥ 执行Lua脚本
下面代码通过C++执行了一个名为Test.lua
的Lua脚本
#include <iostream>
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
int main()
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
luaL_dofile(L, "Test.lua"); // 读取Lua文件,并压入栈内
lua_close(L); // 关闭lua环境
return 0;
}
♣ 引入头文件
lua.h
、lualib.h
、lauxlib.h
三个头文件,是Lua标准库下调用Lua接口的文件
- lua.h
头文件lua.h声明了Lua提供的基础函数
包括创建新Lua环境的函数、调用Lua函数的函数、读写环境中的全局变量的函数,以及注册供Lua语言调用的新函数的函数等
lua.h声明的所有内容都有一个前缀lua_
(例如lua_pcall)
简单的理解,所有外部调用Lua和Lua调用外部都是使用lua.h内接口对应的功能
- lualib.h
头文件lauxlib.h声明了辅助库所提供的函数
辅助库使用lua.h提供的基础API来提供更高层次的抽象,特别是对标准库用到的相关机制进行抽象
其中所有的声明均以luaL_
开头(例如lual_loadstring)
简单的理解,lauxlib.h是对lua.h提供功能的封装
- lauxlib.h
所有lua相关功能以单独的包存在(例如io、math)
lua运行环境创建后,不包含任何预定义的函数
需要用到哪些功能通过lauxlib.h
内的接口注册
简单的理解,lua运行环境初始时没有任何功能的,想用啥功能,用lauxlib.h
注册一下才能用
♣ Lua的虚拟机
我们通过luaL_newstate()
方法创建了一个Lua虚拟机
lua_State *L = luaL_newstate();
lua_State是一个结构体,存储了一个Lua程序的执行状态信息,Lua和C程序通信的栈就存储在其中
注:
这里先知道他是干嘛的就行了,后面有机会我们再去分析Lua的源码
♣ 注册库
上文我们已经提到了,Lua新创建的环境中是没有任何函数定义的,我们需要注册,luaL_openlibs
方法就是打开所有的标准库以供使用
luaL_openlibs(L);
♥ C++和Lua的数据交互
上文我们已经说到,二者通信是通过栈,接下来我们具体分析一下都需要哪些操作
♣ C++调用Lua方法传递参数
我们先写一段简单的lua代码,一句输出,一个可以将参数输出的方法print_parm
我们C++加载逻辑如下图,在第一个例子的基础上稍作修改,运行后成功的输出了第二张图的效果
调用Lua一共经历了下面几个步骤
- 第一步:加载脚本,接口
luaL_dofile
可以供我们加载lua脚本
参数1:创建好的lua_State
参数2:Lua脚本的路径
luaL_dofile(L, "Test.lua");
- 第二步:获取函数,接口
lua_getglobal
可以供我们获取已经加载好的Lua脚本内的全局字段,获取后会压入栈
参数1:创建好的lua_State
参数2:Lua中的全局变量
lua_getglobal(L, "print_parm");
- 第三步:参数压入栈,接口
lua_pushxxx
可以供我们将Lua函数需要的参数压入到栈中
参数1:创建好的lua_State
参数2:具体对应类型的参数值
lua_pushnumber(L, 10);
lua_pushstring(L, "我真帅");
lua_pushboolean(L, true);
经历了上述三个步骤之后,我们的栈会变成下图的样子
- 第四步:调用Lua函数,接口
lua_pcall
调用我们之前压入栈中的Lua函数
参数1:创建好的lua_State
参数2:函数需要的参数数量
参数3:Lua函数的返回值数量
参数4:错误处理函数,成功返回0,失败返回错误码
lua_pcall(L, 3, 0, 0);
♣ Lua调用C++方法
这一块我们看如何在Lua内调用已经写好的方法,这一次先写C++代码,如下图所示
然后再写一段Lua程序,程序内调用addNum
方法,将参数相加后输出,执行后入下2图显示的输出效果
其中需要重点介绍的步骤如下
- 第一步:准备函数,我们需要预先准备一个要给Lua使用的方法,参数必须是
lua_State
的实例
int AddNum(lua_State *L)
{
int n1 = lua_tonumber(L, -1);
int n2 = lua_tonumber(L, -2);
lua_pushnumber(L, n1+n2);
return 1;
}
- 第二步:注册方法,一定要在加载Lua脚本前通过接口
lua_register
将函数进行注册
lua_register(L, "addNum", AddNum);
参数1:创建好的lua_State
参数2:给Lua映射的方法名
参数3:注册的C++方法名
- 第三步:在Lua中执行方法,在Lua中要使用刚才lua_register方法第二个参数映射的名字来调用方法
num = addNum(10, 15)
♠ C API一些常见函数
上文中我们已经用到了很多去操作数据交换和注册方法的函数,下面我们分类列举一些常用的接口
♥ 压入数据
我们已经了解到了C/C++和Lua的相互调用都是通过栈进行的,其中必不可少的操作就是向栈中压入数据
下面我们列举向栈中压入不同数据的方法
函数名 | 功能 |
---|---|
lua_pushnil | 压入常量nil |
lua_pushboolean | 压入bool值 |
lua_pushnumber | 压入双精度浮点值 |
lua_pushinteger | 压入整型 |
lua_pushlstring | 压入字符串非\0结尾 |
lua_pushstring | 压入字符串\0结尾 |
int lua_checkstack ( lua_State *L, int sz);
注:
栈中至少会有20个空闲的位置,一般情况下够用,特殊情况使用接口lua_checkstack
看是否有足够空间
♥ 查询数据
我们一般以栈顶为参照,第一个元素(最后被压入栈的)索引-1,第二个索引-2,以此类推
C API提供了一系列名为lua_is*
的函数用来判断栈内元素类型,下面列举了一些常见的
函数名 | 功能 |
---|---|
lua_isfunction | 是否为方法(c或lua) |
lua_istable | 是否为lua的table |
lua_isuserdata | 是否为lua的userdata |
lua_isnil | 是否为nil |
lua_isboolean | 是否为bool |
lua_isthread | 是否为thread |
lua_isnumber | 是否为数字 |
lua_isstring | 是否为字符串或数字 |
C API提供了一系列名为lua_to*
的函数用来从栈中获取一个值,下面列举了一些常见的
函数名 | 功能 |
---|---|
lua_toboolean | 获取布尔值 |
lua_tonumber | 获取浮点值 |
lua_tothread | 获取thread |
lua_tolstring | 获取字符串 |
lua_tointeger | 获取整型 |
注:
即使对应栈获取的元素类型不正确,这些函数也不会报错,可能会返回Null
♥ 其他栈操作
除了上述在C语言和栈之间交换数据的函数外,C API还提供了下列用于通用栈操作的
函数
函数名 | 功能 |
---|---|
lua_gettop | 返回栈中元素的个数 |
lua_settop | 将栈顶设置为一个指定的值 |
lua_pushvalue | 将指定索引上的元素的副本压入栈 |
lua_rotate | 将指定索引元素向栈顶转动n个位置 |
lua_remove | 删除指定索引的元素 |
lua_insert | 将栈顶元素移动到指定位置 |
lua_replace | 弹出一个值,并将栈顶设置为指定索引上的值 |
lua_copy | 将一个索引上的值复制到另一个索引上 |
♠ 推送
- Github
https://github.com/KingSun5
♠ 结语
若是觉得博主的文章写的不错,不妨关注一下博主,点赞一下博文,另博主能力有限,若文中有出现什么错误的地方,欢迎各位评论指摘。