在前面的文章中,C 函数操作的数据的生命周期都是在该函数执行期间。有时我们需要保存一些非局部数据,虽然在 C 语言中,我们可以使用全局变量或静态变量来满足非局部变量的持有,但是当我们需要使用 Lua 编写库函数时,就会遇到一些问题:
1. C 语言中无法保存普通的 Lua 值。
2. 如果 Lua 库函数中使用了全局变量或静态变量来保存一些数据,会导致该库在多个 lua_State 中使用受到约束。(因为每个 lua_State 间是相互独立的,而 C 函数中使用的全局变量和静态变量却是共用的,这里会出现数据混乱问题。)
因此 Lua 提供了 C-API ,让 C 语言函数有两个地方可以存储非局部数据:
1. 注册表
2. 上值
经过前面文章的学习,可以知道 Lua 内部存储 “非局部数据” ,则通过 “全局变量” 和 “非局部变量” 。
一、注册表
注册表是一张能被 C 代码访问的全局表。 注册表总是位于伪索引 LUA_REGISTRYINDEX 中。
Lua 中接收索引作为参数的 C-API 都可以接收这个伪索引 LUA_REGISTRYINDEX 。只是有一些需要除外,即对栈操作的 C-API 则不可以,例如:lua_remove 、lua_insert 等。
1、如何操作注册表
对注册表的操作,可以认为是对普通表 table 操作。
可以使用对 table 设置或获取 value 操作的 C-API。
和 table 的约束一样,不能用 nil 作为 key。但注册表更为严格,不能使用数值类型作为 key ,因为 Lua 将数值类型作为引用系统的保留字。
因为注册表是全局使用的一个 table ,不同模块均可以使用他,所以在 “注册表” 中使用一个独一无二的 key 较为关键,这样才不会被其他模块覆盖,导致数据问题。
有几种方式可以实现键的不同:
模块内使用的 key 值,均增加模块名作为前缀。例如一个 “3D-Graphics” 的模块,可以将他的 key 取为 “3D-Graphics-xxx” 。
使用引用系统辅助库为我们生成唯一 key ,这个 key 则为整数类型,这就是上面讲到不能自行创建数值类型 key 的原因。
使用 C 代码中静态变量的地址,这样也可以达到全局的唯一。
下面遍一一举例子进行分享
2、模块中使用自定义 key 值
假设我们要编写一个 “3D 渲染” 相关的库,将它命名为 “3D-Graphics” 。在这个模块中,按照约定所有存入注册表中的自定义 key 值都需要带上 “3D-Graphics-” 前缀。
假设我们需要将值存入到 key 为 “xxx” ,则在注册表的名为 “3D-Graphics-xxx” ,下面便是展示如何将值存入到注册表中。
可以发现,其实对注册表的操作和对 table 的操作并没有太大的区别,只是将索引替换为注册表的伪索引即可。
最后输出为:
3、使用引用系统生成索引
为了避免冲突,Lua 提供了一套引用系统,为此 Lua 引入了两个 C-API 函数。
举个例子
1. 向注册表的申请一个索引同时存入值,然后获取该值,最后释放。
2. 再次申请,会发现会分配同一个 key 值。
运行后输出内容如下:
值得注意
1. 如果将 nil 作为值调用 luaL_ref 都不会创建新的引用,都会返回一个常量引用 LUA_REFNIL 。
2.如果将 LUA_REFNIL 进行释放不会有什么作用,例如下面的代码不会有任何作用。
3.如使用 LUA_REFNIL 进行获取注册表的值,会导致压入一个 nil 值到栈中。
4.引用系统定义了一个 LUA_NOREF ,表示无效的引用。
5. 创建 lua 状态时,注册表中有两个预定义的引用:
通过以下代码可以更加具体的感受到。
输出内容如下
4、使用 C 语言静态变量做唯一 key
这种方法也可以实现在注册表中创建唯一的 key 。只是这种方式需要借助 lua_pushlightuserdata 函数将一个 C 语言指针压入到栈中。
以下的代码进行展示如何使用。
可以看到,将通过 lua_pushlightuserdata 将静态变量指针压入栈作为 key ,然后设置 value ,最后对注册表的设置、获取操作和普通表的操作是一样的。
输出如下所示。
Lua 为了简化这种操作,提供了两个 C-API :
参数 p 是需要压入的静态变量指针,上面代码的操作就可以减少一步,具体代码如下,运行后的效果是一致的
值得一提
注册表没有元表,lua_rawsetp 和 lua_rawgetp 都是原始访问,效率会比普通访问快一些。