在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
(一)通过一个简单的例子来看一下userdata的用法: 写一个C的Lua库,让Lua能够访问C的数组,借助userdata来实现。 (1)VS中新建一个DLL工程,设置好lua库的包含目录、链接库; (2)新建一个源文件main.cpp,代码如下: #include <stdio.h> #include <string.h> extern "C" { #include <lua.h> #include <lauxlib.h> #include <lualib.h> } typedef struct NumArray { int size; double values[1]; }NumArray; // lua语句:newarray(size) extern "C" int newarray(lua_State* L) { int n = luaL_checkint(L, 1); size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double); NumArray* a = (NumArray*)lua_newuserdata(L, nbytes); a->size = n; return 1; // 新建的userdata会压栈 } // lua语句:setarray(userdata, index, value) extern "C" int setarray(lua_State* L) { NumArray* a = (NumArray*)lua_touserdata(L, 1); int index = luaL_checkint(L, 2); double value = luaL_checknumber(L, 3); luaL_argcheck(L, a != NULL, 1, "array excepted"); luaL_argcheck(L, index >= 1 && index <= a->size, 2, "index out of range"); a->values[index - 1] = value; return 0; } // lua语句:getarray(userdata, index) extern "C" int getarray(lua_State* L) { NumArray* a = (NumArray*)lua_touserdata(L, 1); int index = luaL_checkint(L, 2); luaL_argcheck(L, a != NULL, 1, "array excepted"); luaL_argcheck(L, index >= 1 && index <= a->size, 2, "index out of range"); lua_pushnumber(L, a->values[index - 1]); return 1; } // lua语句:getsize(userdata) extern "C" int getsize(lua_State* L) { NumArray* a = (NumArray*)lua_touserdata(L, 1); luaL_argcheck(L, a != NULL, 1, "array excepted"); lua_pushnumber(L, a->size); return 1; } static const struct luaL_reg arraylib[] = { {"new", newarray}, {"set", setarray}, {"get", getarray}, {"size", getsize}, {NULL, NULL} }; extern "C" __declspec(dllexport) int luaopen_array(lua_State* L) { luaL_newlib(L, arraylib); return 1; } (3)编译生成名为array.dll的文件,并将array.dll放在luaforwindows的clibs子目录下,该目录下都是为lua写的c库,或者将其放到本地注册的Lua环境变量的某个目录下; (4)lua测试: require "array" a = array.new(100) print(array.size(a)) for i = 1, 100 do array.set(a, i, i) end print(array.get(a, 10)) 上面代码中的关键函数: void *lua_newuserdata (lua_State *L, size_t size); void luaL_argcheck (lua_State *L,int cond,int arg,const char *extramsg); (二)利用metatable标识userdata来增加代码的安全性 上面的C库是有缺陷的,比如我们怎么确保例子中setarray的第一个参数就是我们想要的数组userdata,而不是别的不相关的userdata呢?userdata是一种lua类型,它可以用来表示宿主语言中的各种自定义类型对象,为了区分特定类型,我们使用的方法是: 所以我们对上面的例子进行一些改进,给数组userdata添加一个类型标识,C库代码如下: #include <stdio.h> #include <string.h> extern "C" { #include <lua.h> #include <lauxlib.h> #include <lualib.h> } typedef struct NumArray { int size; double values[1]; }NumArray; // lua语句:newarray(size) extern "C" int newarray(lua_State* L) { int n = luaL_checkint(L, 1); size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double); NumArray* a = (NumArray*)lua_newuserdata(L, nbytes); // 获取预先创建好的metatable,并设置给新建的userdata luaL_getmetatable(L, "LuaBook.array"); lua_setmetatable(L, -2); a->size = n; return 1; // 新建的userdata会压栈 } // 辅助函数,检查数组userdata的metatable是否为LuaBook.array(可理解为是否是LuaBook.array类型的userdat) static NumArray* checkarray(lua_State* L) { void* ud = luaL_checkudata(L, 1, "LuaBook.array"); luaL_argcheck(L, ud != NULL, 1, "array expcected"); return (NumArray*)ud; } // 辅助函数,获取索引处的指针 static double* getelem(lua_State* L) { NumArray* a = checkarray(L); int index = luaL_checkint(L, 2); luaL_argcheck(L, index >= 1 && index <= a->size, 2, "index out of range"); return &a->values[index - 1]; } // lua语句:setarray(userdata, index, value) extern "C" int setarray(lua_State* L) { double newvalue = luaL_checknumber(L, 3); *getelem(L) = newvalue; return 0; } // lua语句:getarray(userdata, index) extern "C" int getarray(lua_State* L) { lua_pushnumber(L, *getelem(L)); return 1; } // lua语句:getsize(userdata) extern "C" int getsize(lua_State* L) { NumArray* a = checkarray(L); lua_pushnumber(L, a->size); return 1; } static const struct luaL_reg arraylib[] = { {"new", newarray}, {"set", setarray}, {"get", getarray}, {"size", getsize}, {NULL, NULL} }; extern "C" __declspec(dllexport) int luaopen_array(lua_State* L) { // 创建数组userdata将要用到的metatable luaL_newmetatable(L, "LuaBook.array"); luaL_newlib(L, arraylib); return 1; } 上面代码中的关键函数: void luaL_newmetatable (lua_State *L, const char *tname); void *luaL_checkudata (lua_State *L, int index, const char *tname); void luaL_getmetatable (lua_State *L, const char *tname); void luaL_setmetatable (lua_State *L, const char *tname); int lua_getmetatable (lua_State *L, int index); void lua_setmetatable (lua_State *L, int index); (三)将上面的代码改造成面向对象的方式 类型为对象的userdata,可以使用如下的语法来操作对象的实例: require "array" a = array.new(100) print(getmetatable(a)) print(a:size()) for i = 1, 100 do a:set(i, i) end print(a:get(10)) 思路大致如下: (1)array表只包含一个方法,也就是用来生成数组对象的new方法; (2)数组userdata带有metatable用于类型识别; (3)userdata的metatable定义__index,那么,每当访问数组的方法时,都会触发__index这个metamethod(对于userdata来讲,每次被访问的时候元方法都会被调用,因为userdata根本就没有任何key); (4)将metatable.__index设为该表metatable本身(__index可以为函数或者表,这里使用后者); (5)metatable包含其余所有的数组操作函数。 那么每当调用userdata的某个方法时,比如a:size(),它等同于a.size(a),这时会触发userdata的名为__index的metamethod,metatable的__index就是它本身,而metatable表中有size域,所以调用metatable的size(a)函数,就ok了。 #include <stdio.h> #include <string.h> extern "C" { #include <lua.h> #include <lauxlib.h> #include <lualib.h> } typedef struct NumArray { int size; double values[1]; }NumArray; // lua语句:newarray(size) extern "C" int newarray(lua_State* L) { int n = luaL_checkint(L, 1); size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double); NumArray* a = (NumArray*)lua_newuserdata(L, nbytes); // 获取预先创建好的metatable,并设置给新建的userdata luaL_getmetatable(L, "LuaBook.array"); lua_setmetatable(L, -2); a->size = n; return 1; // 新建的userdata会压栈 } // 辅助函数,检查数组userdata的metatable是否为LuaBook.array(可理解为是否是LuaBook.array类型的userdat) static NumArray* checkarray(lua_State* L) { void* ud = luaL_checkudata(L, 1, "LuaBook.array"); luaL_argcheck(L, ud != NULL, 1, "array expcected"); return (NumArray*)ud; } // 辅助函数,获取索引处的指针 static double* getelem(lua_State* L) { NumArray* a = checkarray(L); int index = luaL_checkint(L, 2); luaL_argcheck(L, index >= 1 && index <= a->size, 2, "index out of range"); return &a->values[index - 1]; } // lua语句:setarray(userdata, index, value) extern "C" int setarray(lua_State* L) { double newvalue = luaL_checknumber(L, 3); *getelem(L) = newvalue; return 0; } // lua语句:getarray(userdata, index) extern "C" int getarray(lua_State* L) { lua_pushnumber(L, *getelem(L)); return 1; } // lua语句:getsize(userdata) extern "C" int getsize(lua_State* L) { NumArray* a = checkarray(L); lua_pushnumber(L, a->size); return 1; } // metatable的tostring函数 int array2string(lua_State* L) { NumArray* a = checkarray(L); lua_pushfstring(L, "array(%d)", a->size); return 1; } // 表本身只包含一个new方法 static const struct luaL_reg arraylib_f[] = { {"new", newarray}, {NULL, NULL} }; // 这些方法注册在metatable里面 static const struct luaL_reg arraylib_m[] = { {"__tostring", array2string}, {"set", setarray}, {"get", getarray}, {"size", getsize}, {NULL, NULL} }; extern "C" __declspec(dllexport) int luaopen_CLibraryForLua_Userdata(lua_State * L) { // 创建metatable,堆栈+1 luaL_newmetatable(L, "LuaBook.array"); // 设置自metatable的__index为metatable本身 // 复制metatable lua_pushvalue(L, -1); // mt.__index = mt lua_setfield(L, -2, "__index"); // 注册metatable的函数 luaL_setfuncs(L, arraylib_m, 0); // 创建array表,只有一个new函数 luaL_newlib(L, arraylib_f); return 1; } (四)以数组下标的形式访问 怎样实现支持下表操作的语法来访问userdata呢,就像下面一样: require "array" a = array.new(100) a[10] = 3 print(a[10]) 可以直接在lua中通过以下代码实现: local metaarray = getmetatable(newarray(1)) metaarray.__index = array.get metaarray.__newindex = array.set 对应到C中的实现方式如下: static luaL_Reg arraylib_f[] = { {"new", newarray}, {NULL, NULL} }; static luaL_Reg arraylib_m[] = { {"__tostring", array2string}, {"__newindex", setarray}, {"__index", getarray}, {"__len", getsize}, {NULL, NULL} }; extern "C" _DLLExport int luaopen_CLibraryForLua_Userdata(lua_State * L) { luaL_newmetatable(L, "LuaBook.array"); luaL_setfuncs(L, arraylib_m, 0); luaL_newlib(L, arraylib_f); return 1; } 将metatable的__index设为array的get方法,__newindex设为set方法即可。在读取a[i]的时候会触发__index,并将对象本身和参数同时传递给__index对应的函数,写a[i]的时候原理一致。 (五)light userdata light userdata不同于full userdata,它有如下特点: 因为它是一个值,任何指向同一个C地址的light userdata都相等。 void lua_pushlightuserdata (lua_State *L, void *p); (六)userdata相关的资源释放 Lua以__gc元方法的方式提供了finalizers。这个元方法只对userdata类型的值有效。当一个userdata将被收集的时候,并且userdata有一个__gc域,Lua会调用这个域的值(应该是一个函数):以userdata作为这个函数的参数调用。这个函数负责释放与userdata相关的所有资源,比如说文件描述符、窗口句柄等。
luaL_newlib(L, arraylib); |
请发表评论