lua作为脚本于要能够使用宿主语言的类型,不管是宿主基本的或者扩展的类型结构,所以Lua提供的UserData来满足扩展的需求。在Lua中使用宿主语言的类型至少要考虑到几个方面:

  1. 数据内存
  2. 生命周期
  3. 数据操作

    下面的内容主要参考《Lua程序设计》,数据保存在Lua堆栈中,通过Metatable对数据进行操作,并通过Lua的Gc进行回收内存。

1 Full UserData

void *lua_newuserdata (lua_State *L, size_t size);

This function allocates a new block of memory with the given size, pushes onto the stack a new full userdata with the block address, and returns this address.Userdata represents C values in Lua. A full userdata represents a block of memory. It is an object (like a table): you must create it, it can have its own metatable, and you can detect when it is being collected. A full userdata is only equal to itself (under raw equality).When Lua collects a full userdata with a gc metamethod, Lua calls the metamethod and marks the userdata as finalized. When this userdata is collected again then Lua frees its corresponding memory.

函数按照指定的大小分配一块内存,将对应的userdatum放到栈内,并返回内存块的地址。Userdata可以有自己的metatable ,如果Metatable中有__Gc元方法,回收时会调用改方法。回收之后Lua释放对应的内存。

void *lua_touserdata (lua_State *L, int index);

If the value at the given acceptable index is a full userdata, returns its block address. If the value is a light userdata, returns its pointer. Otherwise, returns NULL.

操作实例
//结构定义
typedef struct NumArray {
int size;
double values[1]; /* variable part */
} NumArray; //创建数组
static 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; /* new userdatum is already on the stack */
} //设置数组数值set(a, index, value)
static 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' expected");
luaL_argcheck(L, 1 <= index && index <= a->size, 2, "index out of range");
a->values[index-1] = value;
return 0;
}
//获取数组元素 get(a,index)
static 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' expected");
luaL_argcheck(L, 1 <= index && index <= a->size, 2,"index out of range");
lua_pushnumber(L, a->values[index-1]);
return 1;
} // 库定义
static const struct luaL_reg arraylib [] = {
{"new", newarray},{"set", setarray},{"get", getarray},{NULL, NULL}
};
int luaopen_array (lua_State *L) {
luaL_openlib(L, "array", arraylib, 0);
return 1;
}

导入上面的库,我们可以在Lua中进行数组的操作:

a = array.new(1000)   --创建
array.set(a, 1, 10) --设置
print(array.get(a, 10)) -- 获取

2 MetaTable

为了区分不同C类型的userData,可以为userData添加不同的metatables。每次我们访问时候,我们都要检查他是否有一个正确的 metatable。 Lua 代码不能改变 userdatum 的metatable,所以他不会伪造我们的代码。

操作函数
int luaL_newmetatable (lua_State *L, const char *tname);

创建新表(将用作 metatable),将新表放到栈顶并建立表和 registry 中类型名的联系

void luaL_getmetatable (lua_State *L, const char *tname);

获取 registry 中的 tname 对应的 metatable,压入栈中

void *luaL_checkudata (lua_State *L, int index, const char *tname);

检查在栈中指定位置的对象是否为带有给定名字的 metatable 的 userdata。如果对象不

存在正确的 metatable,返回 NULL,否则,返回userdata的地址

修改案例
//首先创建名字为LuaBook.array的meta 表
int luaopen_array (lua_State *L) {
luaL_newmetatable(L, "LuaBook.array");
...
}
//创建数组时,为Userdata添加元表方法
static int newarray (lua_State *L) {
...
luaL_getmetatable(L, "LuaBook.array");
lua_setmetatable(L, -2);
...
}
//添加检测metatable名字是否正确的接口
static NumArray *checkarray (lua_State *L) {
void *ud = luaL_checkudata(L, 1, "LuaBook.array");
luaL_argcheck(L, ud != NULL, 1, "`array' expected");
return (NumArray *)ud;
}

也就是说创建相关类型的metatable,然后在创建UserData的时候设置metatable,在获取时首先检测meta是否正对

3 面向对象访问

可以通过扩展userdata中metatable的元方法来实现对C对象进行面向对象方式的访问方式。

C中定义
// Metatable增加__index , __newindex方法
int luaopen_array (lua_State *L)
{
luaL_newmetatable(L, "LuaBook.array");
lua_pushstring(L, "__index");
lua_pushvalue(L, -2); /* pushes the metatable */
lua_settable(L, -3); /* metatable.__index = metatable */ //第一次调用,当我们传递一个NULL作为库名时,luaL_openlib并没有创建任何包含函数的表;相反,他认为封装函数的表在栈内,位于临时的upvalues的下面
luaL_openlib(L, NULL, arraylib_m, 0); //根据给定的数组名创建一个新表,并在表中注册指定的函数
luaL_openlib(L, "array", arraylib_f, 0);
return 0;
} // 定义Userdata中的方法
static const struct luaL_reg arraylib_f [] = {
{"new", newarray}, {NULL, NULL}
}; //定义Metatable中方法
static const struct luaL_reg arraylib_m [] = {
{"set", setarray},{"get", getarray},{NULL, NULL}
};

通过为Metatable扩展元方法的方式实现对象结构在Lua中访问方便。

luaL_openlib(L, NULL, arraylib_m, 0)应该是将arraylib_m 中的方法添加到metatable中,有点不好理解,其实也可以用settable的方式将set、get方法注入到metatable中。

Lua 中定义

另外也可以在Lua中设置userdata的元表,不过需要在array库导入之后执行。

function RegsterMetatable(a)
local metaarray = getmetatable(a)
metaarray.__index = metaarray
metaarray.set = array.set
metaarray.get = array.get
metaarray.size = array.size
end

3 关于full Userdata的生命周期

上面定义的操作流程 是通过lua_newuserdata 在Lua中创建内存,并返回地址给C,C提供方法对内存进行操作。通过UserData的Metatable方式为Lua中访问提供便利。但是,C中如果保存了lua_newuserdata 返回的指针,那么Lua中释放之后,C中保存的指针就会失效. 关于UserData的生命周期问题可以参考下云风的文章

4. Light UserData

一个light userdatum是一个表示C指针的值(也就是一个void *类型的值)。由于它是一个值,我们不能创建他们,使用函数lua_pushlightuserdata将一个light userdatum入栈:

void lua_pushlightuserdata (lua_State *L, void *p);

Light userdata不是一个缓冲区,仅仅是一个指针,没有metatables。像数字一样,light userdata不需要垃圾收集器来管理她。有些人把light userdata作为一个低代价的替代实现,来代替full userdata,但是这不是light userdata的典型应用。首先,使用light userdata你必须自己管理内存,因为他们和垃圾收集器无关。第二,尽管从名字上看有轻重之分,但full userdata实现的代价也并不大,比较而言,他只是在分配给定大小的内存时候,有一点点额外的代价。

Light userdata真正的用处在于可以表示不同类型的对象。当full userdata是一个对象的时候,它等于对象自身;另一方面,light userdata表示的是一个指向对象的指针,同样的,它等于指针指向的任何类型的userdata。所以,我们在Lua中使用light userdata表示C对象。

在云风的博客中找到的一些应用场景:

[Lua 中写 C 扩展库时用到的一些技巧][http://blog.codingnow.com/2006/11/lua_c.html]

[向 lua 虚拟机传递信息][http://blog.codingnow.com/2006/01/_lua.html]

[去掉 full userdata 的 GC 元方法][http://blog.codingnow.com/2013/08/full_userdata_gc.html]

结束语

本来不想去花时间写一些书上应该有的,不过在看ulua的实现中总是对其中的各种userdata的使用完全理解明白,所以还是来认真总结一下。当然中间也做了很多其他功课,后面会对其应用做进一步的介绍。

补充:

Lua5.1支持userdata中使用__gc元方法,Lua5.2之后对普通table也支持,在设置Api交互的时候比较便利。具体可以查看:

[Lua GC 之 Finalizer][http://www.cnblogs.com/JesseFang/archive/2012/12/27/2836160.html]

Lua 与 C 交互之UserData(4)的更多相关文章

  1. Lua和C++交互详细总结

    转自:http://cn.cocos2d-x.org/tutorial/show?id=1474 一.Lua堆栈 要理解Lua和C++交互,首先要理解Lua堆栈. 简单来说,Lua和C/C++语言通信 ...

  2. 用好lua+unity,让性能飞起来——lua与c#交互篇

    前言 在看了uwa之前发布的<Unity项目常见Lua解决方案性能比较>,决定动手写一篇关于lua+unity方案的性能优化文. 整合lua是目前最强大的unity热更新方案,毕竟这是唯一 ...

  3. Lua和C++交互 学习记录之九:在Lua中以面向对象的方式使用C++注册的类

    主要内容转载自:子龙山人博客(强烈建议去子龙山人博客完全学习一遍) 部分内容查阅自:<Lua 5.3  参考手册>中文版 译者 云风 制作 Kavcc vs2013+lua-5.3.3 在 ...

  4. [转载]Lua和C++交互详细总结

    原文请看:Lua和C++交互详细总结 转自:http://cn.cocos2d-x.org/tutorial/show?id=1474 一.Lua堆栈 要理解Lua和C++交互,首先要理解Lua堆栈. ...

  5. Lua和C交互的简易教程

    转载请标明出处:http://blog.csdn.net/shensky711/article/details/52458051 本文出自: [HansChen的博客] Lua栈 要理解Lua和C++ ...

  6. Lua与C++交互初探之Lua调用C++

    Lua与C++交互初探之Lua调用C++ 上一篇我们已经成功将Lua的运行环境搭建了起来,也成功在C++里调用了Lua函数.今天我来讲解一下如何在Lua里调用C++函数. Lua作为一个轻量级脚本语言 ...

  7. Lua 和 C 交互中虚拟栈的操作

    Lua 和 C 交互中虚拟栈的操作 /* int lua_pcall(lua_State *L, int nargs, int nresults, int msgh) * 以保护模式调用具有" ...

  8. Lua和C++交互 学习记录之八:C++类注册为Lua模块

    主要内容转载自:子龙山人博客(强烈建议去子龙山人博客完全学习一遍) 部分内容查阅自:<Lua 5.3  参考手册>中文版 译者 云风 制作 Kavcc vs2013+lua-5.3.3 1 ...

  9. Lua和C++交互 学习记录之七:C++全局函数注册为Lua模块

    主要内容转载自:子龙山人博客(强烈建议去子龙山人博客完全学习一遍) 部分内容查阅自:<Lua 5.3  参考手册>中文版 译者 云风 制作 Kavcc vs2013+lua-5.3.3 1 ...

随机推荐

  1. Netty心跳机制

    一.概念介绍网络中的接收和发送数据都是使用操作系统中的SOCKET进行实现.但是如果此套接字已经断开,那发送数据和接收数据的时候就一定会有问题.可是如何判断这个套接字是否还可以使用呢?这个就需要在系统 ...

  2. docker学习系列(二):使用Dockerfile创建自己的镜像

    dockerfile可以允许我们自己创建镜像,通过编写里面的下载软件命令,执行docker build 即可生成镜像文件. 初尝dockerfile 新建一个目录test,然后进入这个目录,创建一个名 ...

  3. Notyf - 超级简单、响应式的 JS 通知插件

    通知是网站的常用功能之一,可以用来显示消息.通告.提示等等.Notyf 是一款超级简单.响应式的 JS 通知插件,不依赖 jQuery 库,可以独立使用.赶紧试用一下吧! 在线演示      免费下载 ...

  4. 深入学习使用ocr算法识别图片中文字的方法

    公司有个需求,简单点说需要从一张图片中识别出中文,通过python来实现,当然其他程序也行,只要能实现,而小编主要学习python,所以就提了python.一个小白在网上遨游了一天,终于找到一丝丝思绪 ...

  5. spring-boot-2.0.3启动源码篇二 - run方法(一)之SpringApplicationRunListener

    前言 Springboot启动源码系列还只写了一篇,已经过去一周,又到了每周一更的时间了(是不是很熟悉?),大家有没有很期待了?我会尽量保证启动源码系列每周一更,争取不让大家每周的期望落空.一周之中可 ...

  6. mybatis教程1(基本使用)

    官方网站 一.什么是 MyBatis ? MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果 ...

  7. ELK(elasticsearch+kibana+logstash)搜索引擎(二): elasticsearch基础教程

    1.elasticsearch的结构 首先elasticsearch目前的结构为 /index/type/id  id对应的就是存储的文档ID,elasticsearch一般将数据以JSON格式存储. ...

  8. openssl签署和自签署证书的多种实现方式

    openssl系列文章:http://www.cnblogs.com/f-ck-need-u/p/7048359.html 1.采用自定义配置文件的实现方法 1.1 自建CA 自建CA的机制:1.生成 ...

  9. 强烈推荐:240多个jQuery插件

    概述 jQuery 是继 prototype 之后又一个优秀的 Javascript 框架.其宗旨是—写更少的代码,做更多的事情.它是轻量级的 js 库(压缩后只有21k) ,这是其它的 js 库所不 ...

  10. 跨站请求伪造CSRF(Cross-site request forgery)

    CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站 ...