« 宗教与科学(转载) | 返回首页 | lua 代码的断点调试 »

Lua 中写 C 扩展库时用到的一些技巧

今天方舟组的同事问到我一些 lua 的问题,主要是关于 C 扩展库的。我觉得有些技巧性的东西挺值得跟大家分享一下,那么就写篇 blog 吧。

通常,C 扩展库中 C 代码会有一些数据要放在 lua 状态机中。Lua 提供的方案是放在它的 注册表 中。如文档所言,因为 Lua 的注册表是全局共享的,选择 key 的时候就要千万小心了。整数 key 已经被 reference 系统用掉了,一般我们会采用字符串作 key 。

从 C 中压入字符串的效率不是最高,这是因为外部字符串进入状态机时需要重新 hash 并检查唯一性所致。关于避免直接压入字符串的问题,以前写过一篇 blog 谈过。( btw, 以前那个方法也不是最好的解决方案,不过还是可以作为一个参考的 :)

很容易想到,最方便且能保证唯一性的 key 是一个 light userdata 。这一点,在参考手册以及 Programming in Lua 中都有提到。

我们可以用一个 static 变量的地址作为 key 在注册表做索引保存需要的值。比如如下代码:

static const void *key = 0; lua_pushlightuserdata(L, (void *)&key); lua_pushnumber(L, myNumber); lua_settable(L, LUA_REGISTRYINDEX);

这样就把一个 C 中的数字 myNumber 放在注册表中了。如果以后要读出来当然可以用这样的代码:

lua_pushlightuserdata(L, (void *)&key); lua_gettable(L, LUA_REGISTRYINDEX); myNumber = lua_tonumber(L, -1);

这里用的是一个 static 变量的地址作 key ,所以绝对不会和被的扩展库冲突。但是这样做有一个缺点,就是 key 这个东西以一个全局变量形式出现,不太美观。而且,当我们想用到这个 key 的时候,不是那么容易拿到。要么,你得 extern 出这个 key ;要么你需要以一个单件的形式暴露一个函数拿到这个 key ,效率上或许又打了一些折扣了。

现在就给出一个折中的方案,或许能让绝大多数人满意: 那就是,以一个 light userdata 做 key ,这个 key 本身再和一个唯一字符串关联起来。每次再需要拿到 key 指针的函数中这些写:

static const void *key = 0; if (key==0) { lua_getfield (L, LUA_REGISTRYINDEX, "MyExtensionLibrary"); key=lua_touserdata(L,-1); lua_pop(L,1); if (key==0) { key=(void*)&key; lua_pushlightuserdata(L,key); lua_setfield(L, LUA_REGISTRYINDEX, "MyExtensionLibrary"); } }

这样,我们就可以对 key 做一次惰性初始化了。以上只是示例,初始化部分可以做成一个函数和宏方便重复调用。


顺便再提一个容易被忽略的小技巧: 我们通常用 full userdata 来实现复杂的 C 结构,或是 C++ 中的对象,把它们用于 Lua 中。经常我们需要把 Lua 中的一堆数据关联在这个 userdata 上。为了让 Lua 的 gc 过程可以正确的工作,我们需要做一些额外的工作。

最下策的解决方案是,在 userdata 中保存一个相关的 Lua 表的 reference ,然后给 userdata 加上一个元表,实现一个 gc 的元方法。在 gc 事件发生时,unref 掉这个 Lua 表。这个方案最糟糕的地方在于,实现过于繁琐。而且给 gc 过程带来许多额外的执行开销。

中策是在注册表中保留一个映射表,并把它设置为 weak table 。每次 userdata 创建出来,就以 userdata 为 key 在这个影射表中保存相关联的 Lua 数据。那么这样,垃圾回收的过程就可以由 Lua 本身自行维护了。其不方便之处在于,每次 C 函数想访问 userdata 相关的 Lua 数据时,需要先拿到注册表中的这个映射表(这个操作或许用的到这篇 blog 前半部分提到的东西)。

其实我们有一个上策,Lua 设计的时候就考虑到了。那就是 full userdata 可以拥有一个独立的环境表。这个东西对 Lua 本身毫无意义。但是却参与 gc 。我们只需要用 lua_getfenvlua_setfenv 来读写这个表就够了 :D

Comments

I just wanted to comment your blog and say that I really enjoyed reading your blog post here. It was very informative and I also digg the way you write! Keep it up and I'll be back to read more soon mate

我用的办法一般都是把c++对象的地址放在lightuserdata里,用的时候直接取对象指针出来,因为对象一般都是外层的成员,所以外部析构的时候内部顺便析构,基本上没有注册过对象到lua里。

差不多就是需要调用 windows api的支持。 你有没有现成的 这个dll接口? 发送一个给我,非常的感谢~~

问一个关于lua的问题, 将lua跟应用程序编译在一起,程序在运行的时候调用lua脚本。 如何用lua脚本向自己写的这个程序发送消息? 比如键盘消息鼠标等

以前没注意过`lua_getfenv`和 `lua_setfenv`这两个函数,发现可以用来实现类似pascal里with object do的功能

谢谢提供!

lua好在哪里?

唉,我错过了网易的笔试
17号晚上网易发来笔试短信,18号9点前要我飞到西安去,我所在城市离西安近千里....

云风是住在杭州的古敦路附近吗?有天上班在路上看见一长发男子酷似你,呵呵,你现在还是长发?

lua好在哪里?

Post a comment

非这个主题相关的留言请到:留言本