Lua的C语言API属性

作为一门嵌入式语言,Lua提供了丰富的CAPI与宿主进行通信。了解更多的CAPI特性能帮助我们更好的发挥Lua虚拟机丰富的功能。


C模块注入Lua

扩展Lua能力的两种结构

  1. 宿主语言在创建lua_State之后,给其全局表注入功能函数,之后通过lua脚本进行调用完成功能(这种一般比较少见)
  2. 将具体的功能封装成库,以C语言模块的形式提供,lua脚本通过require接口去加载,并调用相应功能函数完成任务

这两种结构都会用到CAPI!


C对象在Lua中的表示

  1. 所有 API 平坦的接入 Lua 中,对象指针以 lightuserdata 存在,对象创建和销毁也以 API 形式提供,由 Lua 去控制其生命周期
  2. 对象以userdata的形式存在,C 层就使用 Lua 的元表来模拟面向对象,几乎所有操作都在元表中实现

我们可以根据目标库的接口使用方法来选择比较简洁的一种注入形式!


关于EXTRA_STACK和LUA_MINSTACK

  1. LuaVM调用元方法所使用的Lua栈是有专门预留的,大小为EXTRA_STACK,通常是5个slot(工程师无需为触发元方法保留栈空间)
  2. 每次Lua去Call一个C函数的时候,会给该C函数提供至少LUA_MINSTACK的栈空间大小,通常是20个slot,这20个不包括参数在内(连续嵌套多层C函数则共享这20个slot,从而需要工程师自己去明确把握当前栈空间大小)

编写C模块需要考虑的问题

设计层面:

  1. 形式一:对象内存由Lua去创建,以userdata形式存在,Lua端负责管理目标对象的内存和生命周期,API以元表形式注入,通过__gc元方法管理生命周期
  2. 形式二:对象内存由C语言端创建,对象指针以lightuserdata形式存在,所有API平坦的注入Lua,Lua不管对象的生命周期,由工程师自己去调用API操作
  3. 形式三:对象内存由C语言端创建,对象指针放到userdata中,可以将API通过元表形式注入,实现面向对象的操作,通过__gc元方法管理生命周期
  4. 使用userdata的:必须先创建userdata并设置好__gc元表,之后才能创建目标功能的对象,因为创建userdata可能失败,则目标对象会造成内存泄漏
  5. 是否支持协程让出机制?

代码实现层面:

  1. 考虑参数检查
  2. 考虑错误处理(功能函数出错时正确处理Lua元素,Lua函数出错时正确处理功能函数逻辑,返回错误码给Lua?直接抛异常?)
  3. 某些hold住Lua对象的表是否有必要配置成虚表?(例如需要索引封装对象以便后续做统一操作又不想因此hold住这些对象时)
  4. 模块函数内直接调用Lua函数应当使用lua_call,而不是lua_pcall,将错误留给对其感兴趣的使用者(C模块调用外部资源则应该提前设置好__gc原方法保证不会有内存泄漏)
  5. 与Lua的复杂交互需要考虑栈槽是否足够(特别是针对C语言递归函数)

C模块异步回调Lua:

  1. Lua仅支持单线程,异步回调需要保证同一个时间点只有一条线程在操作Lua,可以在特定时机加锁来实现,也可以保证所有回调函数都在Lua所在线程进行回调
  2. 异步回调Lua必须使用lua_pcall,保证异步回调正常返回(如果异步回调被抛异常回滚了,可能导致库函数堆栈异常)
  3. Lua层传递给C库的回调函数有时会使用registry缓存起来,需要考虑释放的时机(其实就是管理好Lua回调函数的生命周期)
  4. 每个异步回调在调用Lua函数前都需要检查Lua栈是否足够,因为此时的栈上下文是不确定的,可能处于某个Lua函数的栈帧中,栈槽不一定够用

API属性表

相关属性含义如下:

  1. 方括号中第一个字段:该API往Lua栈上弹出slot的数量
  2. 方括号中第二个字段:该API往Lua栈上压入slot的数量
  3. 方括号中第三个字段:’-’表示不会抛出异常,’m’表示内存错误(OutOfMemory或者__gc报错),’e’表示可能抛出任意异常,’v’表示手动抛异常
  4. 额外需要的slot数量:不包括调用该API前的栈空间,单独执行该API需要的slot数量
  5. 手动抛出异常的类型是LUA_ERRRUN

核心库API

API名称 基本属性 额外需要的slot数
lua_arith [-(2|1), 1, e] 1
lua_call [-(nargs+1), +nresults, -] 0
lua_callk [-(nargs+1), +nresults, -] 0

核心库PureAPI

不影响栈内容、不需要栈空间、也不抛异常的API:

API
lua_absindex lua_checkstack lua_isstring lua_atpanic
lua_getstack lua_close

辅助库API

API名称 基本属性 额外需要的slot数
luaL_argcheck [-0, +0, v]

辅助库PureAPI

不影响栈内容、不需要栈空间、也不抛出异常的API:

API
luaL_typename

关于luaL_Buffer

luaL_Buffer相关的操作:

  1. luaL_buffinit
  2. luaL_buffinitsize
  3. luaL_addsize
  4. luaL_addchar
  5. luaL_addlstring
  6. luaL_addstring
  7. luaL_addvalue
  8. luaL_pushresult

注意:在操作Buffer的过程中,需要保证Lua栈空间不新增也不减少,因为Buffer相关的API可能会使用不定个数的栈slot来临时存放字符串片段!