C 函数中如何保存 Lua 的数据

零、前言

在前面的文章中,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_removelua_insert 等。

1、如何操作注册表

对注册表的操作,可以认为是对普通表 table 操作。

  • 可以使用对 table 设置或获取 value 操作的 C-API。
  • 和 table 的约束一样,不能用 nil 作为 key。但注册表更为严格,不能使用数值类型作为 key ,因为 Lua 将数值类型作为引用系统的保留字。

因为注册表是全局使用的一个 table ,不同模块均可以使用他,所以在 “注册表” 中使用一个独一无二的 key 较为关键,这样才不会被其他模块覆盖,导致数据问题。

有几种方式可以实现键的不同:

  1. 模块内使用的 key 值,均增加模块名作为前缀。例如一个 “3D-Graphics” 的模块,可以将他的 key 取为 “3D-Graphics-xxx” 。
  2. 使用引用系统辅助库为我们生成唯一 key ,这个 key 则为整数类型,这就是上面讲到不能自行创建数值类型 key 的原因。
  3. 使用 C 代码中静态变量的地址,这样也可以达到全局的唯一。

下面遍一一举例子进行分享

2、模块中使用自定义 key 值

假设我们要编写一个 “3D 渲染” 相关的库,将它命名为 “3D-Graphics” 。在这个模块中,按照约定所有存入注册表中的自定义 key 值都需要带上 “3D-Graphics-” 前缀。

这个约定只是笔者建议的一种 “尽可能” 避免 key 值冲突的解决方案,并非约束也不是避免 key 冲突的绝对方案。因为有可能存在相同名字的模块,或是有些模块不遵循导致恰巧用了相同的 key 。

假设我们需要将值存入到 key 为 “xxx” ,则在注册表的名为 “3D-Graphics-xxx” ,下面便是展示如何将值存入到注册表中。

lua_State *L = luaL_newstate();

const char *key = "3D-Graphics-xxx";

lua_pushstring(L, "江澎涌");
lua_setfield(L, LUA_REGISTRYINDEX, key);

lua_getfield(L, LUA_REGISTRYINDEX, key);
printf("content: %s\n", lua_tostring(L, -1));

lua_close(L);

可以发现,其实对注册表的操作和对 table 的操作并没有太大的区别,只是将索引替换为注册表的伪索引即可。

最后输出为:

content: 江澎涌

3、使用引用系统生成索引

为了避免冲突,Lua 提供了一套引用系统,为此 Lua 引入了两个 C-API 函数。

函数 描述
int (luaL_ref) (lua_State *L, int t); 会将栈顶的一个值弹出,作为 value,然后分配一个唯一的整型数作为 key ,以 “注册表[key]=value” 的形式保存,最后将整型数 key 返回,后续的更新、获取和移除都使用该返回的 key 进行操作。
void (luaL_unref) (lua_State *L, int t, int ref); 释放 value 和 key ,会将 key 回收,后续 luaL_ref 可以重新利用回收的 key ,同时清空 value 。

举个例子

  1. 向注册表的申请一个索引同时存入值,然后获取该值,最后释放。
  2. 再次申请,会发现会分配同一个 key 值。
lua_State *L = luaL_newstate();

lua_pushstring(L, "jiang pengyong");
// 将栈顶弹出然后设置值
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
printf("引用系统创建唯一的键: %d\n", ref);
// 获取值
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
printf("name: %s\n", lua_tostring(L, -1));
// 释放引用
luaL_unref(L, LUA_REGISTRYINDEX, ref);

// 释放后在生成,又使用了同一个引用
lua_pushstring(L, "jiang pengyong!!!\n");
int ref1 = luaL_ref(L, LUA_REGISTRYINDEX);
printf("重新申请一个 key : %d\n", ref1);

lua_close(L);

运行后输出内容如下:

引用系统创建唯一的键: 4
name: jiang pengyong
重新申请一个 key : 4

值得注意

  1. 如果将 nil 作为值调用 luaL_ref 都不会创建新的引用,都会返回一个常量引用 LUA_REFNIL
// 在 lauxlib.h 文件中
#define LUA_REFNIL      (-1)
  1. 如果将 LUA_REFNIL 进行释放不会有什么作用,例如下面的代码不会有任何作用。
// 不会有任何的作用
luaL_unref(L, LUA_REGISTRYINDEX, LUA_REFNIL);
  1. 如使用 LUA_REFNIL 进行获取注册表的值,会导致压入一个 nil 值到栈中。
lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_REFNIL);
  1. 引用系统定义了一个 LUA_NOREF ,表示无效的引用。
// 在 lauxlib.h
#define LUA_NOREF       (-2)
  1. 创建 lua 状态时,注册表中有两个预定义的引用:
  • LUA_RIDX_MAINTHREAD 指向 Lua 状态本身,也就是主线程。
  • LUA_RIDX_GLOBALS 指向全局变量。
  • 24
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值