lua作为脚本于要能够使用宿主语言的类型,不管是宿主基本的或者扩展的类型结构,所以Lua提供的UserData来满足扩展的需求。在Lua中使用宿主语言的类型至少要考虑到几个方面:
1、数据内存
2、生命周期
3、数据操作
一、Lua中userdata分两类
1、是轻量级userdata(light userdata),轻量级userdata是一种表示C指针的值,对Lua虚拟机来说,这种数据类型不需要GC(垃圾回收),其指向的内存由用户分配和释放,其实现就是一个void *p指针。
实现:lightuserdata通过LUA的API(lua_pushlightuserdata())创建,返回一个指针,自己管理分配和回收。
2、userdata类型完全userdata(full userdata),内存是由Lua虚拟机分配,并有GC机制负责处理。
实现:userdata通过Lua的API(lua_newuserdata())分配内存,就像C里的malloc()函数分配内存,但不需要调用free()去释放内存,该内存是由LUA的GC机制进行回收。
| full userdata | light userdata |
定 义 | 用户自定义数据 | 一种表示C指针的值(即void *),因为是一个值,所以不用创建 |
使 用 | 需要显式的创建一块儿内存,该段内存由Lua的垃圾回收器管理,使用者无需关心。 | 存储在栈上,它所使用的内存空间不由Lua的垃圾回收器管理,所以使用者需要关心其内存使用。 |
创 建 | // 没有进行参数合法性检查 void *lua_newuserdata(lua_State *L, size_t size);
// 有参数合法性检查 void *luaL_checkudata (lua_State *L, int arg, const char *tname); | void lua_pushlightuserdata(lua_State *L, void *p); |
其 他 | 可以指定其”metatable”和”metamethods” | 不能指定其”metatable”和”metamethods”。 |
userdata 是一种用户自定义数据,用于表示一种由应用程序或 C/C++ 语言库所创建的类型,可以将任意 C/C++ 的任意数据类型的数据(通常是 struct 、指针和类)存储到 Lua 变量中调用。
二、light userdata
轻量级userdata的真正用途是相等性判断。一个完全userdata是一个对象,它只与自身相等。而一个轻量级userdata则表示了一个C/C++指针的值。因此,它与所有表示同一个指针的轻量级userdata相等。可以将轻量级userdata用于查找Lua中的C/C++对象。
使用轻量级的userdata结合static来实现无冲突的key
#include <iostream>
#include <string.h>
using namespace std;
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
int main(int argc, char* argv[])
{
lua_State* L = luaL_newstate();
// 压入轻量级userdata,一个static变量的地址
// 由于静态变量的地址在一个进程中具有唯一性,所以绝对不会出现重复key的问题。
static char key = 'k';
lua_pushlightuserdata(L, (void *)&key);
lua_pushstring(L, "JellyThink");
lua_settable(L, LUA_REGISTRYINDEX);
// 从注册表中取对应的值
lua_pushlightuserdata(L, (void *)&key);
lua_gettable(L, LUA_REGISTRYINDEX);
string str = lua_tostring(L, -1);
cout << str.c_str() << endl;
system("pause");
return 0;
}
Lua 提供了一个 注册表, 这是一个预定义出来的表, 可以用来保存任何 C 代码想保存的 Lua 值。 这个表可以用有效伪索引 LUA_REGISTRYINDEX 来定位。 任何 C 库都可以在这张表里保存数据, 为了防止冲突,你需要特别小心的选择键名。 一般的用法是,你可以用一个包含你的库名的字符串做为键名, 或者取你自己 C 对象的地址,以轻量用户数据的形式做键, 还可以用你的代码创建出来的任意 Lua 对象做键。 关于变量名,字符串键名中以下划线加大写字母的名字被 Lua 保留。
三、full userdata
Lua中面向对象的方式
1、新建创建对象函数,调用lua_newuserdata,创建一个对象指针,指向new出来的新的对象。得到注册成员方法时,创建的元表StudentClass,设置元表到创建的Student对象指针,这样通过":"操作符就可以找到对应的方法了。
2、新建成员方法,调用lua_checkudata,除了把对象指针转换为userdata之外,还可以检查是否有"StudentClass"的元表,增加程序健壮性,得到从lua中传入的对象指针,调用成员方法。
3、新建一个元表metatable,并设置元表"__index"的元方法的为metatable本身,然后将成员方法添加到元表metatable里。
4、调用luaL_newlib,新建一个表,把构造函数注册进去。
5、在Lua中,会首先调用创建对象函数,获得Student对象指针。通过Student对象指针,调用成员方法。
使用student_obj:get_age()其实相当于student_obj.get_age(student_obj)
示例:
创建一个lua文件:lua3.lua,调用C++注册的类
local student_obj = Student.create()
student_obj:set_name("Jack")
student_obj:print()
print("age: " .. student_obj:get_age())
--使用内部的__tostring函数进行打印
print(student_obj)
--下面的代码也是可行的
--student_obj.set_name(student_obj,"Jack")
--student_obj.print(student_obj)
--让其进行自动gc
student_obj = nil
--手动强制gc
--collectgarbage("collect")
四、总结
1、lua中的userdata类型主要用来表示在C/C++中定义的类型,即用来实现扩展lua,这些扩展代码通常是用C/C++来实现的。对lua 虚拟机来说userdata只是提供了一块原始的内存区域,可以用来存储任何东西,并且在lua中userdata没有任何预定义的操作。注意这块分配的额外内存是由Lua垃圾收集器来管理的,无须关心起释放等情况。
2、userdata的元表通常用来存储对应C/C++类型的方法,这样在脚本中可以直接调用userdata对应的方法,并且这些方法也是用C/C++实现的。
3、创建一个新的userdata时,其环境表默认值是空,即成员值env 为NULL,当前这个成员在Lua没被使用。在一些文章指出,这个成员可以保存与userdata实例相关的数据,而其对应的元表保存userdata的方法。