为 lua 封装 C 对象的生存期管理问题
把 C 里的对象封装到 lua 中,方便 lua 程序调用,是很常见的一项工作。
里面最大的问题是生命期管理问题。
通常有两种方案:
第一:编写 C 库的时候,完全针对 lua 设计,所有对象都有 lua_newuserdata
分配内存。对象和对象之间的联系可以使用 userdata 的 环境表,把对象间的引用放在里面,使得 lua 的 gc 过程可以正常进行。
第二:给 C 对象简单加一个壳。lua 的 userdata 中仅仅保存 C 对象指针。然后给 userdata 设置 gc 元方法,在被回收时,正确调用 C 对象的销毁函数。
以上两种方案都依赖 lua 的 full userdata ,这里,我想提供第三种方案,仅使用 lightuserdata 完成这项工作。
这第三方案未必比前两种都好。虽然从字面上理解 light userdata 比 full userdata 更廉价,但诚如 pil 中所言,full userdata 也非过于重量。
最终的方案选择还是要结合实际的设计,仔细考量。
方法很简单:
如果你可以保证,所有对象用户只从 lua 层面创建,并依赖 lua 层的 gc 机制销毁。那么仅需要在 lua 中维护一张弱表,把每个创建出来的 lua 封装对象(一般是一个 table)放在这张表中(其实是一个集合)。
同时,在 C 中也维护一个集合(一个简单的对象指针数组即可)。每次对象创建,便把 C 对象指针放入集合。
这样, C 里的集合引用的对象一定是 lua 中那个集合的超集。下面,仅需要周期性的对比两个集合,把 C 集合中多余的对象销毁掉即可。
真正使用时,尤其是前面提到的前提(所有对象只能从 lua 中管理)不满足时,还需要考虑更多细节,这里不再赘述了。
3 月 14 日 补充:
可以通过向 lua 的对象集合(一个弱表)中放置一个 C 收集器来实现在 lua gc 后自动回收 C 对象。这个 C 收集器实现简单,用一个 userdata 绑定一个 gc 元方法即可。
需要注意的是,创建 C 对象和创建 lua 对象,并将两者绑定需要是一个原子操作。否则中间可能被 gc 打断,导致 C 对象被提前回收。
Comments
Posted by: Anonymous | (7) March 15, 2009 10:39 AM
Posted by: 无名 | (6) March 14, 2009 09:58 PM
Posted by: wen | (5) March 14, 2009 05:22 PM
Posted by: Anonymous | (4) March 13, 2009 12:31 AM
Posted by: Anonymous | (3) March 13, 2009 12:30 AM
Posted by: evil84 | (2) March 12, 2009 06:07 PM
Posted by: chu | (1) March 12, 2009 04:52 PM