« 飞机能不能起飞 | 返回首页 | 为什么是周二? »

Lua 中 userdata 的反向映射

lua 中,我们可以用 userdata 保存一个 C 结构。当我们为 lua 写扩展时,C 函数中可以利用 lua_touserdata 将 userdata 转换为一个 C 结构指针。

但是,有时候我们却需要把一个指针转换回 lua 中的 userdata 对象。用到它的最常见的地方是封装 GUI ,通常 GUI 的底层是用 C 编码的。当 engine 把鼠标位置或是别的消息拦截到以后,消息会被传递到一个 C 对象中。这个时候,我们需要从 C 对象中得到对应的 lua 对象,并触发事件。

常见的方法是在 C 对象中保留一个 lua 对应对象的 reference , lua 利用注册表中的数字 key 制作了一个简易的 reference 系统。可以让 C 对象保留一个对 lua 中某对象的引用,使得 lua 的 gc 系统不会错误的回收掉它。

效率略高的方法是,直接让 lua 的 userdata 为 C 对象分配内存,这样,可以更直接的利用 lua 的 gc 系统。而我们在 C 中则可以直接保存 userdata 的数据指针。这是有点点 tricky 的方法,因为 lua 并不保证 userdata 分配出来的内存不会因为 gc 而移动。不过我向 lua 作者求证过,在很长一段时间里,lua 都没有计划实现一个带移动的 gc 。(实际上,移动的 gc 会导致大量的 lua 已有代码不能使用)

这样,只要你能保证 userdata 不会被回收,就可以直接保留 userdata 内的数据指针并使用它。

接下来,我们便有了更简单的反向映射方法。就是直接在 lua 的注册表中创建一个弱表,用它来保存指针和 userdata 的 pair 。需要反向映射的时候,直接用指针作为一个 lightuserdata 当 key 去查这张表就可以了。

由于我们把这些映射信息放在一个弱表中,那么也就不需要关心解引用的问题了。

因为我们一定是 C 函数来操作 userdata ,这个映射表完全可以放在 C 函数的 upvalue 或是环境中,而不需要每次都从注册表拿到。

ps. 如果嫌查表的方法得到反向映射对象的效率不高,云风曾经还做过一个 lua api 的扩展,仿造 lua_pushlightuserdata 加了一个 api 为 lua_pushuserdata (就是调整一下内部指针就可以了,避免表查询),可以直接把一个你认为一定是 userdata 的数据指针,转换为 lua object 压回堆栈。不过这个被评价为 unsafe and think local 。好象大家都公认为了一点效率提高而加一个不太安全的 api 没有价值。如果你都需要回调脚本了,在 C 里多做一次表查询对效率的影响已经微乎其微了。

我自己写完了也是觉得意义不大,这里就不贴了。

Comments

云风大哥,我是个LUA新手,最近在使用lightuserdata时出了一些问题,不知道如何解决: 1.我用C定义了一个LUA库,里面有如下函数: new(lua_State *L)//此函数为分配一个结构体的内存空间,并压入栈 setValue(lua_State *L)//此函数用来设置结构体成员的值 printValue(lua_State *L)//此函数用来打印出结构体的值 然后我在脚本文件中如下调用: local lstruct = mylib.new()//mylib 为我的库名 mylib.setValue(lstruct, 2, 3)//改变结构体的值 mylib.printValue(lstruct)//输出结构体的值 在脚本中调用最后一步时,输出的未赋值的,然后我下断点一步步调试,第调用第2条脚本语句的时候,确实已经将值赋给结构体了 且可以保证,new出来的结构体指针和更改结构体值的指针、输出结构体值的指针是指向的同一个地址,也就是同一个结构体 最后我不知如何解决,把给结构体赋值的函数语句直接整合到输出结构体值的函数中,可以正确输出所赋的值,我就是想不明白,为什么把赋值的语句单独成一个函数时,赋过的值就没了? 是垃圾回收机制回收了吗?
我写的移动gc 是一个简陋的译词。指在垃圾收集过程,会在内存中移动数据。即还在使用的内存块也可能经过 gc 后被移动。
什么是移动gc?
我一般不直接用 lightuserdata 指向的数据 。lightuserdata 只是做 key 用。 userdata 已经可以很好的封装数据了,我一般在一个 userdata 里直接存放引用的别的 userdata 的数据指针。然后把被引用的 userdata 放到其环境表中。这样从属 userdata 的生存期可以保证和宿主 userdata 相同,不需要特别为 宿主 写 gc 元方法了。
userdata通常都是这么处理的; 感觉麻烦的在于lightuserdata - 在lua里用起来和userdata没什么区别, 但是销毁时却得显式的去在自己的dtor里从registry里unregister.

Post a comment

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