您现在的位置是:首页 >其他 >Lua学习笔记:C/C++和Lua的相互调用网站首页其他

Lua学习笔记:C/C++和Lua的相互调用

因吹斯听的Sun同学 2024-07-09 10:31:09
简介Lua学习笔记:C/C++和Lua的相互调用
前言
本篇在讲什么

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.hlualib.hlauxlib.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压入字符串非结尾
lua_pushstring压入字符串结尾
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

♠ 结语

若是觉得博主的文章写的不错,不妨关注一下博主,点赞一下博文,另博主能力有限,若文中有出现什么错误的地方,欢迎各位评论指摘。

? 本文属于原创文章,转载请评论留言,并在转载文章头部著名作者出处?
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。