![6be93099627f1c190fb20f5209e05ab4.png](https://i-blog.csdnimg.cn/blog_migrate/f6f86103736396349ff9eb9df5042faa.jpeg)
Lua在游戏中是非常常见的嵌入式语言,常用来做ui界面,ai逻辑脚本,手机热更等.最近一个项目中用到了lua,需要自己写一些lua的库.这块其实并不是很难,这里简单总结下.
建议对照lua的文档阅读 https://www.runoob.com/manual/lua53doc/contents.html, 里面有每个函数具体功能的解释.
以及lua的源码,lua.c就是C中调用lua的一个解释器实现,lmathlib.c和lstrlib.c就是lua的模块实现.
最后再推荐一本书,讲的非常详细:Programming in Lua, Fourth Edition.
从C中启动Lua
启动一个Lua State并处理输入:
#include
lua.h是Lua的主要头文件,包含了所有Lua提供的基础函数,里面的函数都是lua_开头.
luaxlib.h是Lua的辅助库头文件,里面的函数都是通过lua.h中提供的函数来实现的,是为了方便编程实现的一个辅助库,里面的函数都以luaL_开头.
luaL_newstate新建一个Lua State并返回一个指针,几乎所有的Lua交互函数都需要传入一个lua_State的指针作为第一个参数.
新建出来的Lua State是没有Lua的math,io等这些库甚至print函数的,luaL_openLibs是加载Lua自带的一些库.
栈的操作
Lua和C交互的最大特色就是使用一个虚拟的栈来实现,虚拟的栈上可以是任意类型的值,包括nil,number,string,userdata等.
Lua内部所有的值类型都是用TValue这种结构来实现的,TValue包括Value和_tt,_tt标识Value的类型,Value是一个Union,可以表示不同类型的值,userdate和string是*gc表示,lightuserdata是*p表示.
/*
默认情况下栈满足LIFO.大部分函数需要通过索引index指定一个栈上元素的位置作为参数,索引的位置可以是正数或者负数,正数表示从栈底计数,负数表示从栈顶计数.比如一个有4个元素的栈的索引:
![8df023ae2d95c1cce5c0ee38baf71246.png](https://i-blog.csdnimg.cn/blog_migrate/30dcbc73846af74abe9839677ecc4032.png)
Push 元素:
void
检查栈上空余格子数:
int
检查栈中元素类型:
int
获取栈上的元素:
lua_Number
Dump栈:
static
操作栈元素:
int
一个示例:
int
全局变量:
int
表:
int
从C中调用Lua函数:
假设我们有一个全局的Lua函数:
function
我们在C中加载了这个Lua函数,现在来调用它:
double
int lua_pcall (lua_State *L, int nargs, int nresults, int msgh);
调用lua_pcall前,需要将要调用的函数,参数依次入栈. nargs表示参数的个数, nresults表示期望返回值的个数, Lua会自动裁剪或者用nil补齐返回值.lua_pcall将函数和所有参数出栈,再将所有返回值依次入栈.
如果调用过程中出现错误,lua_pcall会返回一个错误码, 并尝试调用一个错误处理函数, msgh可以指定一个错误处理函数的位置,为0时表示使用默认的错误处理函数.错误处理函数会将一个错误消息入栈.
不管是调用函数还是进行其他的操作,都有义务去保持栈和操作前一样.
从Lua中调用C函数
从Lua中调用C函数时,C函数必须满足下列的格式:
typedef
值得注意的是,调用函数时的栈是一个私有栈,栈上的参数总是从索引1开始的.执行完函数后,需要将所有参数出栈,再将所有返回值入栈,并返回返回值的个数.
一个简单的例子:
static
现在把这个函数注册成为全局函数:
lua_puscfunction
这样我们就可以直接在Lua代码中使用这个函数了.
当然大部分情况下,我们还是用模块的方式来提供一组函数,我们在Lua中可以加载这个模块.
要加载模块,就要先在C中写好注册模块的代码:
static
把模块编译成一个dll或者so文件,文件的名字应该和模块名字一致. 比如在windows来用mingw来编译一个dll,假设我们的文件名字叫mylib.c.
gcc -shared -I $LUAPATH/src -o mylib.dll mylib.c $LUAPATH/src/lua53.dll
在lua中就可以直接用require这个库(函数名称必须是 luaopen_mylib):
local
如果你不想在Lua中require这个模块,可以把luaopen_mylib放到源码linit.c下的luaL_openlibs的标准库列表里面.
LuaL_Buffer:
LuaL_Buffer是一个用来辅助操作字符串的字符串缓存,有两种使用方式.
一种使用方式是预分配大小:
const
来看先Lua在lstrlib.c中实现的string.upper:
static
另外一种方式是变长使用luaL_Buffer:
void
来看一个简单版本的table.concat实现:
static
注册表:
注册表是一个只能在C代码中访问的一个没有元表的表,用于在模块间共享数据.
注册表通过伪索引 LUA_REGISTRYINDEX来访问,大部分的Lua API的索引值都支持用伪索引来调用,除了像lua_insert,lua_remove这种直接操作栈的API.
获取注册表上的一个值:
lua_getfield
因为注册表是所有模块共用的,选用string作为key值时,要注意防止冲突.
无论在何种情况下,都不可以直接用一个自定义的数值作为注册表的key值,Lua提供了引用系统来提供保证唯一的key:
int
上面的函数会将栈顶元素出栈,并得到一个不冲突的整型key值,存储到注册表中.得到的整型key值叫做引用.
lua_rawgeti
引用系统会对nil值特殊处理,如果我们尝试对nil值获取引用,luaL_ref不会创建新的引用,而是返回固定的引用LUA_REFNIL.同样,用LUA_REFNIL作key值取表中的元素也会返回nil.
上值:
注册表提供了全局变量的访问,上值实现了相当于C中的static变量.当我们在Lua中创建一个新的C函数,可以给它关联任意数量的上值.当我们调用函数时,可以使用伪索引来访问这些上值.
这些带有上值的C函数叫做闭包,C闭包和Lua闭包很相似.通常,我们可以使用相同的函数代码,用不同的上值来生成不同的闭包.
来看一个计数器的示例:
c1
C代码:
static
lua_pushcclosure创建一个新的闭包,第二个参数是它的基础函数,第三个参数是上值的个数.
lua_upvalueindex是一个宏,返回一个上值的伪索引.
闭包最多可以拥有255个上值.如果尝试取一个不存在的上值,会返回LUA_TNONE.
有的时候,我们需要再一个模块之间的所有函数中共享变量.除了使用注册表,还可以使用上值.
不像Lua中的闭包,C闭包无法共享上值.但是我们可以把上值设置为一个公共的table,用来共享数据.
luaL_newlib是一个这样的宏定义:
#define luaL_newlib(L, lib) (LuaL_newlibtable(L, lib), LuaL_setfuncs(l, lib, 0))
luaL_setfunc的最后一个参数可以指定栈上的上值个数,为所有函数添加上值.
创建一个共享table的模块:
luaL_newlibtable
自定义类型
再C中自定义类型,通过userdata来实现,userdata分为full userdata和light userdata两种.
Userdata:
void
lua_newuserdata初始化分配的内存会在对象GC时自动释放掉.
每个userdata都可以像table那样设置元表,所以在C中写自定义类型和在Lua中没有太大区别.
一个实现布尔数组的示例:
#include
在Lua中试验一下:
local
Light Userdata:
上面普通的userdata叫full userdata,另外一种userdata就是light userdata.
一个light userdata就是一个C指针 void*值.light userdata就是一个值,而不是一个对象,lightuserda不会有GC,表现行为和number类型是类似的.
Light userdata不能给每个值指定一个元表.但是和Lua中的值类型一样,可以共享一个全局的元表,默认情况下light userdata没有元表.
void
Light userdata的两个值相等,当且仅当它们指向的地址相同.因此light userdata常用来做表的key值,比如注册表的key.
Light userdata另外的用处就是用来映射到一个C对象,实现代理的功能.
--END--