Lua 调用的 C 函数保存 state 的两种方式: Storing State in C Functions 笔记 - yanbin's Blog

Lua 调用的 C 函数保存 state 的两种方式: Storing State in C Functions 笔记

yanbin posted @ 2015年5月24日 23:37 in Programming with tags linux c c++ c/c++ programming Lua , 8664 阅读
使用 Lua call C 编程,不免需要保存一些 non-local data 在 C 函数中; non-local data 来自 Lua state.
然而,直接使用 C static/global 变量保存 state 是有问题的: (a) C 变量不能保存一个 generic Lua value;
(b) static/global 变量的方式也无法用于 multiple Lua states, 因为不同 Lua state 调用相同函数/库时,
需要 C 函数保存的 state 是独立不相关的。
 
C API 提供了两个地方来保存 non-local data: registry 和 upvalues.
 
Registry
Registry 是一个 global table, 只能在 C 函数中使用 Lua C API 提供的函数和 pseudo-index 访问。
pseudo-index 就像一个普通的 stack index 一样,不同的是它所关联的 value 并不真正的存放在 stack 之上。
访问 registry 的 psedu-index 定义 LUA_REGISTRYINDEX. 对 registry 的访问就像是对 stack 访问一样,
Lua C API 提供的大多数函数都可以用于 registry —— 指定 LUA_REGISTRYINDEX 作为 index ——,
只有 lua_remove(), lua_insert() 这些操作 stack 本身的函数除外。例如:
lua_getfiled(L, LUA_REGISTRYINDEX, "SOME_KEY");
 
既然 registry 是一个 regular table, 那么就可以用所有 index 访问它,当然 nil 是不行的。
使用 registry  index 的选用一定要谨慎,毕竟 registry 是全局的,而且所有库和函数都可以共享的 table;
为避免 key 冲突,应当:
(a) 不要使用 number 作为 key;
(b) 使用 string 作为 key, 尽量加上一些特殊的前缀,比如: LIBRAY_NAME_ 这样的;
(c) 使用 reference system;
 
Reference system 是 auxilibrary 提供的一系列函数,使用这些函数可以保证存储 value 到 table
无须关心 key 的唯一性。如下方式可创建一个新的 reference:
int r = luaL_ref(L, LUA_REISTRYINDEX);
这个函数 pop 一个 value 从 stack, 并用新的 index 保存到指定的 table 中,这里 LUA_REISTRYINDEX
关联到 registry, 所以这个调用可保存一个 value 到 registry.
luaL_ref() 函数会将 key 返回,用于以后的对 table 的访问以及 release reference.
luaL_rawgeti(L, LUA_REGISTRYINDEX, r);
luaL_unref(L, LUA_REGISTRYINDEX, r);
 
指定给 luaL_ref() 的 value 是 nil —— 或者说栈顶的 valu 为 nil ——,luaL_ref() 返回 LUA_REFNIL,
使用 LUA_REFNIL 调用 luaL_unref() 和 lua_rawgeti() 都没有任何效果,算得上人畜无害。
另外一个 reference system 定义的常量是 LUA_NOREF, 这个常量保证不同于任何 valid reference,
可用来判断一个 reference 是否为 invalid reference.
 
reference system 有用之处就在于: 我们不能使用 C pointers 指向任何一个 lua object(包括 string),
这样的引用是未定义行为,但是我们可以使用 reference system 将对象存储在 registry 中,
并且维护这个 reference, 就像使用 C pointer 一样。
 
另外一种选择 key 的方式是:使用 C library 中的 static 变量的地址作为 key(的创建源)。
C 链接器可以保证这些变量的地址是唯一的, 常量的大概不行,比如:字符串常量,宏定义的常量。
不过直接用 static 变量的地址作wei index 是错的,得玩点黑魔法:
lua_pushlightuserdata();或 lua_rawsetp(), lua_rawgetp();
 
示例代码:
/* variable with a unique address */
static char key = 'k';
/* store a string */
lua_pushlightuserdata(L, (void *)key); /* push address */
lua_pushstring(L, myStr); /* push value */
lua_settable(L, LUA_REGISTRYINDEX); /* registry[&kye] = myStr */
/* retrieve a string */
lua_pushlightuserdata(L, (void *)key); /* push address */
lua_gettable(L, LUA_REGISTRYINDEX); /* retrieve value */
const char *myStr = lua_tostring(L, -1); /* convert to C string */
 
使用 lua_rawsetp(), lua_rawgetp() 的方式
/* variable with a unique address */
static char key = 'k';
/* store a string */
lua_pushstring(L, myStr);
lua_rawsetp(L, LUA_REGISTRYINDEX, (void *)&key);
/* retrieve a string */
lua_rawgetp(L, LUA_REGISTRYINDEX, (void *)&key);
const char *myStr = lua_tostring(L, -1);

以上这些创建 key 以访问 table 规则对普通 table 也适用。
 
 
Upvalues
Registry 提供了 gblobal 变量给 C function 而 upvalues 机制更象是提供了 static 变量给 C function.
Upvalues 机制主要实现了将 upvalues 与 C functions 相关联,每一个 upvalue 可以保存一个 Lua value.
主要方式是使用 C function 创建 C closure, 并且提供 upvalues 给 C closure.
C closure 的使用与 Lua closure 非常相似,实际 Lua 代码中就是当做 closure 来使用的。
Upvalues 机制可以使用相同的 C function clode 以及不用 upvalues 创建多个不同的 closure.
 
示例代码:
首先是一个工厂函数,这个函数用来创建 C closure.
static int counter(lua_State *L);
int newCounter(lua_State *L)
{
     lua_pushinteger(L, 0);
     /* 使用 counter 作为 base function 创建 C closure, 讲 stack TOP N 作为 upvalues */
     lua_pushcclosure(L, &counter, 1);
     return 1;
}
 
黑魔法函数是 lua_pushcclosure(), 其第二个参数制定用来创建 C closure 的 base function,
第三个参数指定 stack TOP N 作为 upvalues.
NOTE TAHT:必须保证 stack 中前 N 个 value 是要作为 upvalues 的,不然是错误。
 
看看 base function 中是如何访问 upvalues 的。
static int counter(lua_State *L)
{
     int val = lua_tointeger(L, lua_upvalueindex(1));
     lua_pushinteger(L, ++val);
     lua_pushvalue(L, -1); /* duplicate new vlaue */
     lua_replace(L, lua_upvalueindex(1));
     return 1;
}
 
访问 upvalues 也是用 pseudo-index 的方式,真正访问的值并不保存在 stack.
lua_upvalueindex(N) 访问一个 pseudo-index 关联于第 N 个 upvalue。
NOTE THAT:
绝对不能用负数访问 lua_upvalueindex(), 为了避免这样错误可以用 luaL_argcheck() 函数检查参数。
 
 
Shared upvalues
C closure 间不能像 Lua closure 那样共享 upvalues;
为了做到这一点可以让 C closure 间共享一个 table 作为 upvalue, 从而共享了 upvalues.
具体黑魔法要先看 luaL_newlib() 的定义, 这是一个宏定义:
#define luaL_newlib(L, lib) \
     (luaL_newlibtable(L, lib), luaL_setfuncs(L, lib, 0))
关键点是 luaL_setfuncs() 这个函数讲 statck top N 个 value 作为 upvalues 指定给 library 中的函数。
luaL_newlib() 定义中这个 N 是 0, 这个函数是用不成了。不过可以用如下方式:
/* create library table  */
luaL_newlibtable(L, lib);
/* create shared value */
lua_newtable(L);
/* add functions in list 'lib' to the new library,
* sharing previous table as upvalue
*/

luaL_setfuncs(L, lib, 1);

 

参考:
Programming in Lua third edition 28.3
 

Avatar_small
gmail sign up 说:
2018年12月26日 10:48

They are impressive statistics. A very interesting article. Thank you.

Avatar_small
Programming Assignme 说:
2019年1月29日 12:46

Sometimes get troubles with these assignments and projects. While Java is still a fair programming language as compared to other languages, it is still very complicated to make assignments and projects. Students often take Programming Assignment Help from the online writing services to help them out from this difficult situation.

Avatar_small
starjack io 说:
2019年8月23日 14:14

I know your expertise on this. I must say we should have an online discussion on this. Writing only comments will close the discussion straight away! And will restrict the benefits from this information.

Avatar_small
starjack io 说:
2019年8月23日 14:15

Your blog provided us with valuable information to work with. Each & every tip of your post is awesome. Thanks a lot for sharing. Keep blogging,


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter
Host by is-Programmer.com | Power by Chito 1.3.3 beta | © 2007 LinuxGem | Design by Matthew "Agent Spork" McGee