云风的个人空间 : 在 Lua 中管理 C 对象的生存期[LuaReferObject]

首页 :: 索引 :: 修订历史 :: 你好, 34.201.8.144
你的足迹: » 在 Lua 中管理 C 对象的生存期
在给 Lua 写 C 扩展库的时候,经常需要从 C 中传对象指针到 lua 状态机中。如果只是传递一个指针而不关心其生命期的话,用 lightuserdata 即可。但有时候我们需要让 lua 来负责 C 对象的生命期管理,下面提供的一段代码展现一个思路:用 lua 本身的 gc 机制来管理对象,并允许对象指针在 C 中被引用。

参考说明:[[External Link]http://blog.codingnow.com/2007/10/lua_c_object_reference.html]

/* 
 作者:云风
 代码来源:http://blog.codingnow.com/cloud 
 */
 
static void *dummy=0;
 
struct gc_object {
	void *obj;
	void (*release)(void *);
};
 
static int
release_object(lua_State *L)
{
	struct gc_object *gco=(struct gc_object*)lua_touserdata(L,1);
	gco->release(gco->obj);
	return 0;
}
 
/* 模块初始化 */
void 
cobj_init(lua_State *L)
{
	lua_pushlightuserdata(L,&dummy);
	lua_newtable(L);
		lua_createtable(L,0,1);
			lua_pushstring(L,"v");
			lua_setfield(L,-2,"__mode");
		lua_setmetatable(L,-2);
 
		lua_createtable(L,0,1);
			lua_pushcfunction(L,release_object);
			lua_setfield(L,-2,"__gc");
 
		lua_pushlightuserdata(L,release_object);
		lua_pushvalue(L,-2);
		lua_rawset(L,-4);
 
		lua_pushlightuserdata(L,release_object);
		lua_rawset(L,-3);
 
	lua_settable(L,LUA_REGISTRYINDEX);
}
 
static void
get_reftable(lua_State *L)
{
	lua_pushlightuserdata(L,&dummy);
	lua_gettable(L,LUA_REGISTRYINDEX);
}
 
/* 把 C 对象指针 obj 压入堆栈,如果不提供释放函数 release ,则以 lightuserdata 形式存放。
    如果提供 release 函数,则会在 gc 环节调用 */
void 
cobj_push(lua_State *L,void *obj,void (*release)(void*))
{
	if (release==0) {
		lua_pushlightuserdata(L,obj);
	}
	else {
		get_reftable(L);
			lua_pushlightuserdata(L,obj);
			lua_rawget(L,-2);
 
			if (lua_isnil(L,-1)) {
				struct gc_object *gco;
				lua_pop(L,1);
				gco=(struct gc_object *)lua_newuserdata(L,sizeof(*gco));
				gco->obj=obj;
				gco->release=release;
 
				lua_pushlightuserdata(L,release_object);
				lua_rawget(L,-3);
				lua_setmetatable(L,-2);
 
				lua_pushlightuserdata(L,obj);
				lua_pushvalue(L,-2);
				lua_rawset(L,-4);
			}			
 
		lua_replace(L,-2);
	}
}
 
/* 将堆栈上的对象转换为 C 指针 */
void* 
cobj_get(lua_State *L,int idx)
{
	if (lua_islightuserdata(L,idx)) {
		return lua_touserdata(L,idx);
	}
	else {
		struct gc_object *gco=(struct gc_object *)lua_touserdata(L,idx);
		return gco->obj;
	}
}
 
/* 把 obj 对应在 lua 状态机中的对象引用数量加 1 ,返回新的引用次数,出错返回 -1 */
int 
cobj_addref(lua_State *L,void *obj)
{
	int r=-1;
	get_reftable(L);
	lua_pushlightuserdata(L,obj);
	lua_rawget(L,-2);
	if (!lua_isnil(L,-1)) {
		lua_pushvalue(L,-1);
		lua_rawget(L,-3);
		r=lua_tointeger(L,-1) + 1;
		lua_pop(L,1);
		lua_pushinteger(L,r);
		lua_rawset(L,-3);
	}
	lua_pop(L,1);
	return r;
}
 
/* 把 obj 对应在 lua 状态机中的对象引用数量减 1 ,返回新的引用次数,出错返回 -1 */
int 
cobj_release(lua_State *L,void *obj)
{
	int r=-1;
	get_reftable(L);
	lua_pushlightuserdata(L,obj);
	lua_rawget(L,-2);
	if (!lua_isnil(L,-1)) {
		lua_pushvalue(L,-1);
		lua_rawget(L,-3);
		r=lua_tointeger(L,-1) - 1;
		lua_pop(L,1);
		if (r<=0) {
			lua_pushnil(L);
		}
		else {
			lua_pushinteger(L,r);
		}
		lua_rawset(L,-3);
	}
	lua_pop(L,1);
	return r;
}