« 让游戏用户安全的登陆 | 返回首页 | 想找一个老的 DOS 游戏 »

在 Lua 中管理 C 对象

今天同事在设计引擎的脚本接口时遇到一个问题:需要把 C 对象指针放到 Lua 中,允许 Lua 保存这个指针,并传递给其它模块。

这是给 Lua 写 C 扩展时常见的问题,撇开如何如何将对象的方法导入 Lua 这个更复杂的问题不谈,我主要想说说 C 对象的生命期管理的问题。

一开始的设计是把对象的销毁方法也导入 Lua ,由脚本程序员手工管理。这是很明显的 C 程序员的思路:谁构造谁释放。但在这里是不合适的,不符合带 gc 机制语言的习惯。

我们当然希望脚本更为健壮,不需要考虑对象释放的问题。所以晚上我想了一下,修改了一下这部分的实现。

从效率方面着手,这个问题分两种情况:

第一种情况很简单,C 对象可以被传入 Lua 状态机后,逻辑上可以确保它的指针一定一直有效,程序直到 Lua 状态机本身关闭后,才会删除对象。这种情况我们只需要把 C 对象指针以 lightuserdata 的形式压入堆栈即可。

第二种情况就是,C 对象由脚本创建或获得。在没有地方对其引用之后,对象则应该被删除以释放其占用的资源。这种情况,我们应该使用 fulluserdata ,为其注册 gc 元方法。

不过问题复杂在,引用 C 对象的可以是脚本也可以在 C 代码中。脚本中对 userdata 的引用 lua 状态机会自行解决,但 lua 的 gc 过程并不能直接知道 C 中是否对对象还有引用,这就是我们需要做的工作了。

python 的 C 接口提供了相关的函数,可以在 C 界面上对 PyObject 加减引用。但是 lua 的 gc 是基于根扫描的,状态机中并没有引用计数。很自然的,lua 就没有类似的 C 接口了。

我的解决方法是,在 lua 注册表中创建一个弱表(value 是弱的,而 key 是强的),把 C 对象指针和对应的 fulluserdata 以及它在 C 中的引用数量记入这个表里。然后提供一对 API 对引用计数增减。当引用计数为 0 时,清除关于计数的表项。最终可利用 gc 回收掉已无引用的 C 对象。

详细的程序可以参考我的 wiki 上贴的代码

这里补充几点说明:

所有对象的 gc 元方法是共享的,而不是每次创建 fulluserdata 创建一个新的元表。这是一个简单的优化,可以节省不少的内存。方便起见,这个元表也放在那个弱表内。注意:在 Lua 中,每次压入一个 CFucntion 都会重新分配内存创建一个新对象。所以应该尽可能的共用。

每次从 C 对象指针生成 fulluserdata 时,都会去检查以前是否生成过。这样才能使引用计数统一计算。

代码随手写的,并没有经过严格的测试,如果谁想拿去用可自便,但发现 bug 请通知我修改过来。 :D

Comments

push 在堆栈上的,当函数没有返回时,不是垃圾。

class derived
{
public:
derived()
{
}

~derived()
{
printf("deleted derived (%p)\n", this);
}
};

void derived_release(void*p)
{

}

int main(int argc, char *argv[])
{
lua_State *L = lua_open();
luaL_openlibs(L);

derived * p = new derived;
cobj_init(L);
cobj_push(L,p,derived_release);
cobj_addref(L,p);
cobj_release(L,p);

lua_gc(L, LUA_GCCOLLECT, 0); // collected garbage
lua_gc(L, LUA_GCCOLLECT, 0); // collected garbage
lua_gc(L, LUA_GCCOLLECT, 0); // collected garbage
lua_gc(L, LUA_GCCOLLECT, 0); // collected garbage
lua_gc(L, LUA_GCCOLLECT, 0); // collected garbage
lua_close(L);

return 0;
}

我加了点简单的代码进行测试了一下,在调用了lua_gc(L, LUA_GCCOLLECT, 0);后并没有进行彻底的回收对象,只有在lua_close(L);时才真正的开始回收,请问一下,如何做到调用了lua_gc(L, LUA_GCCOLLECT, 0);就回收所有的垃圾对象?谢谢

什么时候回武汉搞点事业啊,支持下家乡建设,我知道的很多人都想回去,但是没有人牵头,期盼ing,有打算通知我一声,帮你招兵买马

boost::python实现时,对这种跨域的对象生存期管理,很巧妙!

两者是有区别的,但是我不想解释。反正写好了放在这里,什么时候发现有用了,拿去用就是了。

能不能说一下为什么不用 luaL_ref / luaL_unref 而要自己做一个表,而且还是表嵌表

和python的简单封装界面工具搞出来用python有什么优点和趋势

我觉得每次都这样作重复功

不是吧,我之不过忘了在 / 两边加空格,竟然把我变成斜体

有一点不明白。
luaL_ref/luaL_unref 本来就可以进行多次引用的。完全可以任何 C 代码持有这个对象的时候都 luaL_ref 一下。

下次考虑只贴代码不说话 :D

下次建议文字在清晰一点

有点明白你的意思了,你的目的是脚本部分只能申请对象或释放对象,而底层系统负责删除对象(利用GC功能),这个规则我很赞同。而这么多的对象管理讨论都是在朝着这个方向去的时候遇到的实现问题了。

做這個窄的頁面呢?方便在 PDA 上看呀?

用C++就好了,这类问题luabind都解决了。

这里只是解决没有 gc 的语言和有 gc 的语言结合时,对象生命期管理的问题。

无论如何分层,这个问题都是逃不掉的。

另外,遇到问题就分出新的层次不是好的设计。良好的设计应该保持足够少的层次,通常不多于三层。

C 层次的引擎应该是一个足够精简的底层。做复杂的对象管理并非它的强项。所谓引擎也不单指 C 层次上的代码,而包括脚本语言编写的一部分。但是,若在脚本语言编写的基础架构部分也用 C 的思维——手工分配和释放对象,而不利用 GC 来完成,就是设计问题了。

Q: 让脚本能够管理内部对象的目的是什么?

A: 带 gc 机制和元信息的(脚本)语言更适合做对象管理的工作(这里的对象指图象和资源层次之上的对象)。C/C++ 应该做适合它的工作:图象渲染、物理计算、资源管理等。

这些也是引擎的一部分,但并非引擎应该全部用 C 来写。

让脚本能够管理内部对象的目的是什么?难道想不扩展引擎的功能来达到整体功能更新?

我觉得让脚本程序参与管理系统对象生命期的方案得不偿失,至少说明系统没有被合理、有序的分层。一直看到云风被纠缠在这个问题上,呵呵,花这些时间还不如直接扩充系统程序功能来达到目的。或者是扩充系统程序与脚本程序的中间层也可。

一家之言。

嗨,学长,你好。昨天上课的时候看同学拿了一本书<<我的编程感悟>>,我要过来了,昨天晚上和今天一气读完了,太崇拜你了,虽然有的专业知识我不懂。我是山东的一名大三学生,专业是通信工程,学过C,出于个人兴趣,自学C++,我存大多数同学存在问题:眼高手低。呵呵,我是多么想做一名程序员呀,不知道能不能知道你的Q号,我的是18673469。呵呵,今天也和名人近距离沟通了下

请教一下,在C/C++环境中调用lua或者python脚本的机制大概是什么?C/C++与脚本语言的接口是怎么定义的呢?由于我们公司的游戏是自己编写的脚本系统,主要是基于文本解析的方式调用脚本的。所以想知道使用编程语言来写脚本的主要实现原理和方法。能推荐给我一些资料是最好啦。

MARK,再看

Post a comment

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