zoukankan      html  css  js  c++  java
  • Lua Userdata

    (一)通过一个简单的例子来看一下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)
    {
        const char* libName = "array";
        luaL_register(L, libName, 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);
      新建full userdata。
      (1)分配一块指定大小的内存;
      (2)将该full userdata压栈;
      (3)返回该内存块的地址给主机程序,主机程序能够随意使用这块内存。

      void luaL_argcheck (lua_State *L,int cond,int arg,const char *extramsg);
      检查条件是否满足。

    (二)利用metatable标识userdata来增加代码的安全性

      上面的C库是有缺陷的,比如我们怎么确保例子中setarray的第一个参数就是我们想要的数组userdata,而不是别的不相关的userdata呢?userdata是一种lua类型,它可以用来表示宿主语言中的各种自定义类型对象,为了区分特定类型,我们使用的方法是:
      我们单独为该数组创建一个metatable,每次创建数组userdata时,我们设置其和metatable的关联。每次我们访问数组的时候,都检查一下其是否有一个正确的metatable即可。也就是利用不同的metatable来标记不同类型的userdata。因为Lua代码不能够改变userdata的metatable,所以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");
    
        const char* libName = "array";
        luaL_register(L, libName, arraylib);
        return 1;
    }

    上面代码中的关键函数:

      void luaL_newmetatable (lua_State *L, const char *tname);
      创建userdata可用的metatable。
      如果registry已经有tnme键值,则函数返回0;
      否则,创建一个[tname, metatable],并放入registry,并返回1。
      两种情况下,都会讲tname对应的值入栈。
      堆栈+1

      void *luaL_checkudata (lua_State *L, int index, const char *tname);
      检查在栈中指定位置的对象是否为带有给定名字的metatable(registry中键tname对应的值)的usertata。是则返回userdata地址,否则返回NULL。

      void luaL_getmetatable (lua_State *L, const char *tname);
      获取registry中的tname对应的metatable,并入栈。注意区分lua_getmetatable函数。

      void luaL_setmetatable (lua_State *L, const char *tname);
      将栈顶对象的metatable设置为registry表中键tname对应的值。注意区分lua_setmetatable函数。

      int lua_getmetatable (lua_State *L, int index);
      获取index对应的table的metatable,并入栈。如果该table没有metatable,则返回0,且堆栈不变。

      void lua_setmetatable (lua_State *L, int index);
      将栈顶的table出栈并设置给index处的值作为metatable。
      堆栈-1

     (三)将上面的代码改造成面向对象的方式

      类型为对象的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_array(lua_State* L)
    {
        // 创建数组userdata将要用到的metatable
        luaL_newmetatable(L, "LuaBook.array");
    
        // 设置metatable的__index为metatable本身
        lua_pushstring(L, "__index");
        lua_pushvalue(L, -2);
        lua_settable(L, -3);
        // 注册metatable的函数
        luaL_register(L, NULL, arraylib_m);
    
        // 创建array表,只有一个new函数
        luaL_register(L, "array", 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 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_register(L,"array",arraylib);
    
        // 那么现在metatable在栈底,array表在其上的位置
        // metatable.__index = array.get
        lua_pushliteral(L, "__index");
        lua_pushliteral(L, "get");
        lua_gettable(L, 2);
        lua_settable(L, 1);
    
        // metatable.__index = array.set
        lua_pushliteral(L, "__newindex");
        lua_pushliteral(L, "set");
        lua_gettable(L, 2);
        lua_settable(L, 1);
    
        return 0;
    }

      将metatable的__index设为array的get方法,__newindex设为set方法即可。在读取a[i]的时候会触发__index,并将对象本身和参数同时传递给__index对应的函数,写a[i]的时候原理一致。

    (五)light userdata

      light userdata不同于full userdata,它有如下特点:
      (1)full userdata代表Lua中的C对象,light userdata代表一个C指针的值(也就是一个void *类型的值)。由于它是一个值,我们不能创建他们(同样的,我们也不能创建一个数字)。
      (2)仅仅是一个指针,像数字一样,没有metatables,light userdata不需要垃圾收集器来管理她。
      (3)可以用于表示不同类型的对象,我们在Lua中使用light userdata表示C对象。

      因为它是一个值,任何指向同一个C地址的light userdata都相等。

      void lua_pushlightuserdata (lua_State *L, void *p);
      将一个light userdata入栈。

     (六)userdata相关的资源释放

      Lua以__gc元方法的方式提供了finalizers。这个元方法只对userdata类型的值有效。当一个userdata将被收集的时候,并且userdata有一个__gc域,Lua会调用这个域的值(应该是一个函数):以userdata作为这个函数的参数调用。这个函数负责释放与userdata相关的所有资源,比如说文件描述符、窗口句柄等。

  • 相关阅读:
    按照鬼哥学so变化,四,第一章的例子
    浏览器发送总共下载文件2第二个请求,如何“下载”仅仅记录1次要?
    hdu 3371 Connect the Cities
    自己写RTPserver——大约RTP协议
    cocos2d-x3.2中将XCode发展project转移到VS2010可能会发生错误
    apache kafkac系列lient发展-java
    大约++和--了解运营商
    socket计划——一个简单的例子
    PhotoShop基本工具 -- 移动工具
    ZA7783:MIPI转LVDS/MIPI转RGB888/RGB转LVDS
  • 原文地址:https://www.cnblogs.com/sifenkesi/p/3897245.html
Copyright © 2011-2022 走看看