lua中userdata
userdata这东西,可以理解为用户自定义数据. 它是数据,不是类型,其实说白了,就是一片内存. 通过一个简单的API,我们就能获取一个userdata:
view sourceprint? void *lua_newuserdata (lua_State *L,size_t size); 这个API一目了然,创建好的userdata会被妥善安置在lua stack的顶部. 这里有一个很有趣的地方,就是我们能够申请一段由lua管理的内存,我听说lua的gc还是蛮不错的,如果我可以把许多内存管理的工作扔给lua,那真是太好了.? 另一方面,我觉得lua实在是不行,还是自己管理内存比较靠谱,但是我又需要让lua能比较直接的操作我写的C模块所申请的一片内存. 面对2种不同的需求,lua提供的机制都能够让我们一一应对. 1. 申请一片比较大的内存,将实例放在这片内存里. 2. 申请小段内存,在这片内存中保存实例地址,将实例放在C/C++模块申请的内存中. 在情况1中,一旦lua的gc回收内存,C/C++实例就被销毁. 第2种情况下,C/C++实例可以继续存在. 这两种解决方案都有可能被用来解决实际问题,而第2种情况非常值得写一个完整的例子来研究. 不过今天只看一下第1种情况吧. 为了研究第1种情况,我打算做一个简单的float数组. 下面是数组在C中的定义以及一些lua接口: view sourceprint? struct LuaArray view sourceprint? { ??? int size; ??? float data[1];? // 为了简便,我就这么做了. }; void InitArray(lua_State* pState);????? // 这个函数不是向lua提供的接口. 只是用作初始化. int NewArray(lua_State* pState); int ReleaseArray(lua_State* pState); int GetArrayValue(lua_State* pState); int SetArrayValue(lua_State* pState); int GetArrayLength(lua_State* pState); int SumArray(lua_State* pState); 下面是InitArray函数的代码: view sourceprint? static const char* LuaArrayTableName = "LuaArray"; static const luaL_Reg ArrayFunction[] = { ??? {"__newindex",? SetArrayValue}, ??? {"__len",?????? GetArrayLength}, ??? {"__gc",??????? ReleaseArray}, ??? {"get",???????? GetArrayValue}, ??? {"sum",???????? SumArray}, ??? {"new",???????? NewArray}, ??? {NULL,NULL} }; void InitArray(lua_State* pState) { ??? luaL_register(pState,LuaArrayTableName,ArrayFunction); ??? lua_pushvalue(pState,-1); ??? lua_setfield(pState,-2,"__index"); ??? lua_pop(pState,1); }; 我们可以采用类似MFC中消息映射的一些宏来简化LuaArrayTableName和ArrayFunction. 这里,我创建了一个lua table,并让这张表本身作为一张元表,存储于lua_State的context中. 一旦我们创建了这张元表,就能让我们的userdata和这张元表绑定. 这样,我们就能在lua中,对userdata进行元表规定的操作. 先来看一下创建的代码: view sourceprint? int NewArray(lua_State* pState) { ??? int elemCount = luaL_checkint(pState,1); ??? int memSize = sizeof(LuaArray) + elemCount * sizeof(float); ??? LuaArray* pUData = (LuaArray*)lua_newuserdata(pState,memSize); ??? pUData->size = elemCount; ??? pUData->data[0] = 0.0f; ??? for (int i = 1; i <= elemCount; ++i) ??????? pUData->data[i] = 0.0f; ???????? ??? lua_getglobal(pState,LuaArrayTableName); ??? lua_setmetatable(pState,-2); ???? ??? // ---------------------------------------------------------------------------- ??? // 在gc时使用,没有特别的意义. ??? float* pExData = new float[10]; ??? memcpy_s((void*)pUData->data,sizeof(float),(void*)&pExData,sizeof(float*)); ??? // ---------------------------------------------------------------------------- ???? ??? return 1; } 第5行创建了userdata,并在前端存储LuaArray结构. 在lua中,我们用这样的代码就能创建一个LuaArray: view sourceprint? arr = LuaArray.new(10)? -- 创建10个元素的LuaArray 设置LuaArray中的值,获取LuaArray中的值(省去所有检测): view sourceprint? int SetArrayValue( lua_State* pState ) { ??? LuaArray* pUData = (LuaArray*)lua_touserdata(pState,1);??? // 这里可以做一些检测 ??? int idx = luaL_checkint(pState,2); ??? float val = (float)luaL_checknumber(pState,3); ??? pUData->data[idx] = val;???????????????????????????????? ??? return 0; } int GetArrayValue( lua_State* pState ) { ??? LuaArray* pUData = (LuaArray*)lua_touserdata(pState,1); ??? int idx = luaL_checkint(pState,2); ??? lua_pushnumber(pState,(lua_Number)pUData->data[idx]); ??? return 1; } lua中设置和获取值的代码如下: view sourceprint? arr[1] = 100 print(arr:get(1)) // 没有arr[1]的原因在于元表中的__index属性被用来指向元表本身. 其他函数大同小异. 有意思的是__gc事件. 在lua中的变量都是引用,当一个对象没有任何变量引用的时候,就会被lua的gc回收. 在lua中这样写,就会让代码回收: view sourceprint? arr = nil 在相应__gc事件的C/C++函数中,我们就能对刚才申请的内存进行释放: view sourceprint? int ReleaseArray( lua_State* pState ) { ??? LuaArray* pUData = (LuaArray*)lua_touserdata(pState,1); ??? float* pExData; ??? memcpy_s((void*)&pExData,sizeof(float*),(void*)pUData->data,sizeof(float)); ??? delete [] pExData; ??? return 0; } userdata+metatable的机制,让我们能从C/C++的角度为lua提供数据和类型的扩展. 本文中对这套机制的使用方法仅仅是一个简陋的实验方法,具体项目中可以加入许多改进以应对不同需求. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |