Lua和C

ronald10个月前职场4460

一.背景

    在实际游戏业务运营过程中,经常会出现一些紧急的配置修改、bug修复等;这种规划外的变更行为希望能做到用户无感知,否则对于游戏体验和产品口碑有很大影响,在此背景下,热更新能力成为成熟上线游戏的标准配置。

    一般情况下,后台逻辑热更新能力的技术方案有两种:

    ①基于共享内存的数据存储:通过进程状态数据存放在共享内存,使得进程重启数据不丢,可以通过重新attach到共享内存实现进程状态的恢复。通常用这种机制的是C++,优点是效率高,缺点是需要重新编译整个项目,发布操作较重。

    ②后台代码脚本化:利用脚本语言代码热加载的能力,实现在进程运行过程中的逻辑更新,优点是更新操作轻量,可以像更新配置一样实现逻辑更新,但是由于业务逻辑是脚本实现的,在执行效率上会有一定的损耗,为此常规的操作是底层框架和热点逻辑交给C++去实现,而经常变动的业务逻辑则用脚本去写。

二.LUA和C交互的原理

   C和Lua通过虚拟栈进行交互,如下图

11.png

    栈的元素代表一个Lua值(table、string、nil、function等八种基本类型)

Lua和C交互的形式:Lua和C的交互分为数据交互和逻辑交互两类,再叠加两种访问方向组合出四种交互形式。下面逐一进行论述:

1. C访问Lua的数据

        如图所示,Lua会为程序中所有的全局变量建立一个变量名到变量值的映射关系表,这个映射关系就存放在这样一个全局表中,当C想访问Lua中定义的全局变量的时候,调用接口lua_getglobal(),接口里面会先把这个变量名通过Lua的虚拟栈传递给Lua程序,Lua虚拟机拿到这个变量名之后去全局表里查询,查询到之后,加入到Lua虚拟栈中,C调用相关接口获取到这个值。

Lua.png

2. Lua访问C的数据

        Lua访问C的数据本质上还是需要C程序通过接口先将数据压入到虚拟栈,并通过接口lua_setglobal(lua_State *L, const char *name)触发Lua虚拟机从虚拟栈中弹出一个值,并赋值给name这个变量(即在全局表里面建立一个KV结构)(value重复怎么办?)。

3. Lua访问C的接口

        lua访问C的接口分为两种方式:

(1)lua_register

typedef int (*lua_CFunction) (lua_State *L);
void lua_pushcfunction (lua_State *L, lua_CFunction f);
void lua_register (lua_State *L,const char *name,lua_CFunction f);
#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n)))

    ①所有能被Lua调用的C接口,都需要定义成lua_CFunction

    ②lua_pushcfunction用于接收一个C函数的指针作为参数,将一个Lua.function类型的对象压栈

    ③从lua_register定义看,本质上就是将函数压栈,然后在全局表注册

(2)luaL_newlib 

#luaL_newlib用于将C函数以库的形式加载到Lua环境中
void luaL_newlib (lua_State *L,const luaL_Reg l[]);
#用下列宏实现
(luaL_newlibtable(L,l), luaL_setfuncs(L,l,0))
#luaL_newlibtable创建一张新的表,并预分配足够保存下数组l内容的空间,其中l得是一个数组
void luaL_newlibtable (lua_State *L, const luaL_Reg l[]);
#luaL_setfuncs用于把数组l中的所有函数注册到栈顶的表中
#若nup≠0,所有的函数都共享nup个upvalues。这些值必须在注册之前,pushed到栈上。
#这些值在注册完毕后都会从栈弹出。
void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup);

        luaL_newlib是将C语言函数以库的方式加载到Lua环境中

(3)用法示例

#用法示例
/*需要被调用的C语言函数*/
int C_Func_01(lua_State* L)
{
	printf("C_Func_01 is run \r\n");
	return 0;
}
/*定义函数表*/
static const luaL_Reg CFuncLib[] = {
	{"C_Func1",C_Func_01},
	{NULL,NULL}
};
/*创建一个新库*/
int luaopen_CFuncLib(lua_State* L) {
	luaL_newlib(L, CFuncLib);
	return 1;
}
int main(void)
{
	lua_State* L = NULL;
	L = luaL_newstate();
	#若CFuncLib不在package.loaded中,则调用接口luaopen_CFuncLib,将接口注册
	#最后一个参数为1,表示CFuncLib注册到全局变量
	luaL_requiref(L, "CFuncLib", luaopen_CFuncLib, 1);
	#加载并运行指定的文件
	luaL_dofile(L, "test_01.lua");
	lua_close(L);
	return 0;
}
--注意这里,必须以"库名.函数名()"的方式调用,还可以以"库名:函数名()"的方式调用这两种方式稍有不同,下面介绍
CFuncLib.C_Func1()
--或
CFuncLib:C_Func1()

4. C访问Lua的接口


        C调用Lua的接口是通过lua_pcall实现,以下面代码段为例:

lua_getglobal(L, "test_add");
int a = 10;
int b = 20;
lua_pushnumber(L, a);
lua_pushnumber(L, b);
if (lua_pcall(L, 2, 1, 0) != 0) {
    lua_pop(L, 1);
    exit(-1);
}
int sum = lua_tonumber(L, -1);
lua_pop(L, 1);

    在lua脚本中,我们定义了一个test_add的函数,这个test_add在全局表里面有一个KV映射,key为函数名,val为函数的地址,通过lua_getglobal,将函数的入口地址压到栈中,然后C通过接口lua_pushxxx将另外两个参数入栈,然后调用lua_pcall或者lua_call触发lua接口的执行,lua接口执行完毕之后参数和函数地址会自动出栈,并将返回值压栈,因此在函数调用结束之后,通过接口lua_toxxx(L,-1)从栈顶取出返回值,并转成我们期望的格式。

    针对返回值的问题,需要多提一句,实际编码过程中,可能出现实际返回和预期返回值类型不一致的情况,此时应该在进行返回值转换之前,通过接口lua_checktype进行类型检查。

    外一个就是lua_call和lua_pcall的差别,lua_pcall意味着保护模式,lua_call没有返回值,效率相对较高,但是主调接口无法获取函数调用状态,而lua_pcall有返回值并支持设置特定的错误处理函数。如下所示:

void lua_call (lua_State *L, int nargs, int nresults);
int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc);


标签: LUAC

相关文章

Lua的垃圾回收(下)

Lua 5.3版本的垃圾回收Lua垃圾回收源码实现    结合前面描述的垃圾回收的流程,我们参照源码进行逐个的拆解和介绍。    lua的垃圾回收主要都是在接口luaC_step里面完成的,这里面本质上是控制了一个状态机,根据global_State->gcstate进行渐进式的垃圾回收处理。GCSpause&n...

Lua和so

Lua和so

    实际在应用开发过程中,会用到很多第三方的库;Lua由于其易嵌入的特性,不仅可以使用Lua编写的库,也可以将C++的库进行二次封装供Lua调用。这里我们以实现在Lua中解析xml文件格式场景,结合C++编写的“tinyxml2” 这个库为例进行讲解:库的准备及编译第一步,下载“tinyxml2”的源码下载链接:leethomason/tinyxml2:...

几种Lua和C交叉编程的程序写法

Lua程序调用C接口//另一个待Lua调用的C注册函数。 static int sub2(lua_State* L) {     double op1 = luaL_checknumber(L,1);     double op2 ...

Lua热更新机制(下)

Lua热更新机制(下)

Lua热更新机制怎么解决热更的这些问题场景拆分    Lua代码的实体可拆分出三类:配置、逻辑和运行时状态,其中逻辑分为常规的接口和闭包内的逻辑,运行时状态又分为全局变量和闭包内的局部变量,这里结合skynet的解决方案进行描述。热更的核心问题    1)新老版本的差异是什么?   &nb...

LUA数据结构(三)

Lua数据结构userdata    Lua官方的介绍:userdata是一种用户自定义数据,用于表示一种由应用程序或者C/C++语言库创建的类型,可以将任意C/C++类型的数据(通常是struct、指针)存储到Lua变量中调用。    在实际应用过程中,C/C++接口调用LuaL_newuserdata就会分配指定大...

LUA环境搭建

LUA环境搭建

    本文针对Lua新手,介绍Lua开发环境的搭建。环境搭建目标语法高亮,自动补齐语法错误检查方法跳转lua脚本本地运行断点设置及调试功能编辑器选择    主流代码编辑器有vscode、rider、clion。其中下载链接:    Documentation for Visua...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。