|
前言
游戏开发中必不可少就是热更新了,目前的热更新方案主要有两种:基于Lua的热更新解决方案,比如xLua、toLua、sLua、uLua 和 基于C#的热更新方案 HybridCLR,两种方案各有优缺点,本文目前重点不是介绍这些。作为铺垫,其实要想更好的理解基于Lua的热更新方案,明白Lua与C(宿主语言)交互的原理是至关重要的,下面将简单的介绍一下这个过程。
lua和C交互的核心就是lua栈,lua和C的所有数据交互都是通过lua栈来完成的。
1、 C调用lua
C调用lua很简单,通常lua都是作为宿主语言(本例是C)的扩展脚本,在运行时,宿主语言需要读取lua脚本数据,其主要步骤:
1)加载lua脚本 luaL_loadfile
2)运行lua脚本 lua_pcall
3)获取相关数据 lua_getglobal ...
4)使用相关数据 lua_tostring lua_pcall ...
在宿主语言中运行lua脚本的时候,lua也需要能够访问到C(宿主语言)中定义的一些变量或者函数,这个过程大致为:
1)将C变量或函数push到lua栈中
2)通过lua_setglobal为当前lua栈顶的函数或变量命名,这样在lua中可通过该名字完成对变量或函数的使用
3)之后可在加载的lua脚本中使用C变量或函数
2、在lua脚本中调用C
lua中使用C的方式大多数都是通过库文件的形式来实现的,将C函数编译为动态库文件,这样可以在lua主程序中,加载这个库文件,并使用其中的C函数。下面举个简单的例子来说明一下整体的大概流程。
假设要给lua使用的C代码定义在mylib.c中
1)在mylib.c中定义给lua调用的C函数,函数原型一般都是 int (lua_State*) 这样的等等,如:
static int c_addsub(lua_State* L)
{
double a = luaL_checknumber(L,1); // 获取参数1
double b = luaL_checknumber(L,2); // 获取参数2
lua_pushnumber(L, a+b); // 压入返回值1
lua_pushnumber(L, a-b); // 压入返回值2
return 2; // 两个返回值
}2)在mylib.c中定义一个注册函数,用于lua在加载C动态库时,调用该函数完成对库中所导出的C函数的注册,如:
// 将C模块中要导出的C函数放入到luaL_Reg结构体数组内
static const struct luaL_Reg l[] = {
{"addsub", c_addsub},
{NULL, NULL} // 以NULL标识结束
};
// 该函数在导入C库时调用 完成对库中导出的函数的注册 必须是non-static
int luaopen_mylib(lua_State* L)
{
// 完成实际的注册工作
// 注册方式一: luaL_openlib(lua_State* L, const char* name, const luaL_Reg* l, int nup)
// L : lua_State
// name: 表明该C库被加载后,所导出的函数位于哪一个全局table之下
// 这里是"clib" 那么之后lua中通过clib.addsub完成对C函数的调用
// l : 要导出的函数的lua_Reg结构体数组
// luaL_openlib自动将该数组内的name-function对注册并填充到第二参数指定的table下
// nup : upvalue的个数,如果不为0,则注册的所有函数都共享这些upvalues
luaL_openlib(L, "clib", l, 0);
// 注册方式二: luaL_newlibtable + luaL_setfuncs (等价于lua_newlib)
// luaL_newlibtable(L, l);
// luaL_setfuncs(L, l, 0);
// 前两句等价于:
// luaL_newlib(L, l);
// 将包含name-cfunction键值对的table返回给lua
return 1;
}注意上面方式一和方式二的主要区别:前者(luaL_openlib)为name-cfunction对在lua中注册了一个名字(“clib”),而后者(luaL_newlib)没有,它只是将这个table返回给了lua。更多的关于luaL_openlib和luaL_newlib注册方式的差异以及他们各自的注册原理,有深入理解需求的可以参阅官方文档或者其他资料。本质上来讲,其交互核心都是类似的,只是将C作为动态库时,我们需要提供一个”入口函数”,用以在加载该动态库后执行,完成对库中所有导出函数的注册。
3)将相关C文件编译成动态链接库
把一个源码文件编译成动态库很简单,但不同平台编译指令可能不一样,这个可以自行查阅,比如在Linux下,命令大概就是这样的:gcc -c mylib.c
4)在lua中加载C动态库
方式一 : 使用 loadlib
--加载C动态库 并将luaopen_mylib函数 导出到mylib变量中
mylib = loadlib("./mylib.so", "luaopen_mylib")
--调用mylib() 将执行lua_openmylib函数(在上述第二步中提到的函数) 完成对C动态库中所有导出函数的注册
--将C中返回的name-cfunction table赋给clualib变量
clualib = mylib()
--通过clualib完成C函数的调用
sum, diff = clualib.addsub(5.6, 2.4);
--针对于注册方式一,还可以通过luaL_openlib中传入的clib来使用C函数
sum, diff = clib.addsub(5.6, 2.4)方式二 : 使用 require
clualib = require("mylib")
sum,diff = clualib.addsub(5.6, 2.4)
--对于luaL_openlib完成的注册,仍然可以通过clib来访问C函数
sum, diff = clib.addsub(5.6, 2.4)关于loadlib与require工作原理差异,不是本文的重点,有需要的可以查看其他资料或者<<Programing in Lua>>这本经典的书籍。 |
|