分下面几种函数:
- 读写Lua全局变量的函数
- 调用Lua函数的函数
- 运行Lua代码片断的函数
- 注册C函数然后可以在Lua中被调用的函数
一、一个简单的Lua解释器
```cinclude
include
// Lua提供的基础函数 include
// 辅助库(auxlib)提供的函数,都是luaL_开头,是lua.h的基础上的封装(抽象) include
// 定义了打开各种库的api
int main (void) { char buff[256]; int error;
// 创建一个新的Lua环境environment(全局表),几乎是空的,连print都没有// 通过lualib.h的接口,打开各种库,注册到环境table中。lua_State *L = lua_open();luaopen_base(L); // 基础库luaopen_table(L); // table库,这样lua中才能使用tableluaopen_io(L); // io库,lua中才能使用io.write之类的luaopen_string(L); // string库luaopen_math(L); // math库while (fgets(buff, sizeof(buff), stdin) != NULL) {error = luaL_loadbuffer(L, buff, strlen(buff), // 遍历lua代码类似loadstring// 成功,就把chunk压栈// 错误,就把错误消息压栈"line") || lua_pcall(L, 0, 0, 0); // 弹出栈顶lua chunk并执行(保护模式)if (error) {fprintf(stderr, "%s", lua_tostring(L, -1)); // 栈顶数据转成lua stringlua_pop(L, 1); // 弹出栈顶错误消息}}lua_close(L);return 0;
}
<a name="ZOSoK"></a># 二、C和Lua交互栈为什么会用到栈?Lua和C之间交互会出现的问题。<br />1、变量类型匹配问题<br />Lua是动态类型,每个变量的类型运行时会改变。C肯定不能这样,定义了就固定了。<br />2、内存管理问题<br />Lua是有自动内存管理,C堆内存非自动,可能Lua那边已经回收,C这边还在。<br />通过栈来保存Lua值,栈由Lua管理,C通过调用Lua来将C类型值压栈,然后出栈数据给Lua使用。Lua以LIFO原则来操作栈,也就是说只改变栈顶函数。<a name="dOzGt"></a>## 操作栈:压栈、查询API通过索引来访问栈元素,索引规则:<br />正数表示法:1表示第一个压栈元素,也就是**栈底**,2是往上一个。<br />负数表示法:-1表示最后一个压栈元素,也就是**栈顶**,-2是往下一个。```cvoid lua_pushnil (lua_State *L);void lua_pushboolean (lua_State *L, int bool);void lua_pushnumber (lua_State *L, double n);//lua字符串不是以0作为结尾,而必须指明长度。//lua对栈中的字符串,只拷贝,不会以指针来操作。void lua_pushlstring (lua_State *L, const char *s, size_t length);void lua_pushstring (lua_State *L, const char *s);//检查栈是否有能存储sz条记录的空间//lua.h中定义了栈最小空闲空间LUA_MINSTACKint lua_checkstack (lua_State *L, int sz);//index位置的栈元素是否是目标类型。如lua_isnumber,lua_isstring,lua_istable//lua_isnumber和lua_isstring通过是否能转换来判断。//对下面lua_type的封装int lua_is... (lua_State *L, int index);//栈顶元素是否是type类型的lua值//enum_type值如下//LUA_TNIL, LUA_TBOOLEAN, LUA_TNUMBER, LUA_TSTRING//LUA_TTABLE, LUA_TFUNCTION, LUA_TUSERDATA, LUA_TTHREADint lua_type (lua_State *L, enum_type type);//从栈中获取元素,注意没有弹栈int lua_toboolean (lua_State *L, int index); //失败返回0double lua_tonumber (lua_State *L, int index); //失败返回0const char * lua_tostring (lua_State *L, int index); //失败返回0size_t lua_strlen (lua_State *L, int index); //失败返回NULL//lua字符串不是0结尾,而C是,所以C操作从栈中返回的string要特别注意const char *s = lua_tostring(L, -1); //获取luastringsize_t len_lua = lua_strlen(L, -1); //s的真实长度size_t len_c = strlen(s); //c以字符串操作s这是不对的,s中间可能有0字符,//len_c <= len_luaint lua_gettop (lua_State *L); //返回栈元素个数,栈顶整数索引void lua_settop (lua_State *L, int index); //超过index的被丢失,不足的填nil,index=0清空栈#define lua_pop(L,n) lua_settop(L, -(n)-1) //丢失栈顶的n个元素void lua_pushvalue (lua_State *L, int index); //拷贝index元素,压入栈顶。void lua_remove (lua_State *L, int index); //移除index元素void lua_insert (lua_State *L, int index); //栈顶元素插入到index位置,上面的上移。void lua_replace (lua_State *L, int index); //弹出栈顶,并替换index位置的值。
打印栈
// 从栈底到栈顶遍历了整个堆栈,依照每个元素自己的类型打印出其值。它用引号输出字符串;// 以%g的格式输出数字;对于其它值(table,函数,等等)// 它仅仅输出它们的类型(lua_typename转换一个类型码到类型名)static void stackDump (lua_State *L) {int i;int top = lua_gettop(L);for (i = 1; i <= top; i++) { /* repeat for each level */int t = lua_type(L, i);switch (t) {case LUA_TSTRING: /* strings */printf("`%s'", lua_tostring(L, i));break;case LUA_TBOOLEAN: /* booleans */printf(lua_toboolean(L, i) ? "true" : "false");break;case LUA_TNUMBER: /* numbers */printf("%g", lua_tonumber(L, i));break;default: /* other values */printf("%s", lua_typename(L, t));break;}printf(" "); /* put a separator */}printf("\n"); /* end the listing */}
三、C调用Lua
C调用Lua原理比较简单。
1、由lua端设计了栈,并给C提供了栈操作的API
2、通过API,C可以直接将Lua当前环境下的变量入栈。
3、也就很容易让函数、变量压栈,并执行函数,返回值、错误信息再压栈,供C端使用。
C使用Lua配置
场景:C是一个窗口程序,用Lua做颜色主题的配置文件。
具体方法:Lua是用一个全局表做配置文件,C封装直接获取/设置表的域值的函数。
------------颜色主题配置文件------------WHITE = { r = 1, g = 1, b = 1}RED = { r = 1, g = 0, b = 0}background = WHITE
/**********C中预定义的颜色主题配置************/#define MAX_COLOR 255struct ColorTable {char *name;unsigned char red, green, blue;} colortable[] = {{"WHITE", MAX_COLOR, MAX_COLOR, MAX_COLOR},{"RED", MAX_COLOR, 0, 0},{"GREEN", 0, MAX_COLOR, 0},{"BLUE", 0, 0, MAX_COLOR},{"BLACK", 0, 0, 0},...{NULL, 0, 0, 0}};/******************************************//*********C封装backgroud[key]操作********///假设当前栈顶元素为backgroudint getfield (const char *key) { //假设栈顶已经是backgroudint result;lua_pushstring(L, key); //key压栈lua_gettable(L, -2); //key出栈,background[key]压栈if (!lua_isnumber(L, -1)) //检查数值合法性error(L, "invalid component in background color");result = (int)lua_tonumber(L, -1) * MAX_COLOR;lua_pop(L, 1); //栈顶弹出return result;}/*********C封装backgroud[key] = value操作********///假设栈顶是backgroundvoid setfield (const char *index, int value) {lua_pushstring(L, index);lua_pushnumber(L, (double)value/MAX_COLOR);lua_settable(L, -3); //栈顶是value,栈顶-1是key,栈顶-2是background//background[key]=value,key,value弹栈。}/*********C封装backgroud = {r=1,g=2,b=3}操作********///假设栈顶是backgroundvoid setcolor (struct ColorTable *ct) {lua_newtable(L); //创建表tablesetfield("r", ct->red); /* table.r = ct->r */setfield("g", ct->green); /* table.g = ct->g */setfield("b", ct->blue); /* table.b = ct->b */lua_setglobal(ct->name); /* 'name' = table */}/********************封装函数使用***********************//*********把上面C中预定义的常见颜色值加载到lua全局表_G中**********/int i = 0;while (colortable[i].name != NULL)setcolor(&colortable[i++]);/*********C中获取上面lua配置中的background表的r、g、b值**********/lua_getglobal(L, "background"); //全局变量background入栈if (!lua_istable(L, -1)) //检查结果error(L, "`background' is not a valid color table");red = getfield("r"); //backgroud.rgreen = getfield("g"); //backgroud.gblue = getfield("b"); //backgroud.b
C调用Lua函数
场景:C写王者荣耀,Lua计算伤害数值。
--------------Lua函数function f (x, y)return (x^2 * math.sin(y))/(1 - x)end
/* 调用Lua的f函数 */double f (double x, double y) {double z;lua_getglobal(L, "f"); //全部变量f(函数)压栈lua_pushnumber(L, x); //参数x压栈lua_pushnumber(L, y); //参数y压栈//多使用pcall,安全。还有错误处理函数if (lua_pcall(L, 2, 1, 0) != 0) //调用栈中函数,2个参数,1个返回值,没有错误处理函数//返回值,错误信息入栈error(L, "error running function `f': %s",lua_tostring(L, -1)); //输出错误信息//检查返回值数据类型if (!lua_isnumber(L, -1)) error(L, "function `f' must return a number");z = lua_tonumber(L, -1); //获取返回值lua_pop(L, 1); //返回值出栈return z;}
C动态调用Lua函数
动态调用lua函数,函数名,参数类型,参数数量,返回值都运行时决定。
//local ret = f(x,y)//dd>d:double f(double, double)call_va("f", "dd>d", x, y, &z);#include <stdarg.h>void call_va (const char *func, const char *sig, ...) {va_list vl;int narg, nres; //参数数量,返回值数量va_start(vl, sig);lua_getglobal(L, func); //全局函数funcnarg = 0;//根据sig字符串,参数入栈。while (*sig) {switch (*sig++) { //"dd>d":>前面部分case 'd':lua_pushnumber(L, va_arg(vl, double));break;case 'i':lua_pushnumber(L, va_arg(vl, int));break;case 's':lua_pushstring(L, va_arg(vl, char *));break;case '>':goto endwhile;default:error(L, "invalid option (%c)", *(sig - 1));}narg++;luaL_checkstack(L, 1, "too many arguments"); //检查栈空间} endwhile:nres = strlen(sig); //检查返回值数量与sig字符串标识的是否对得上if (lua_pcall(L, narg, nres, 0) != 0) //调用函数error(L, "error running function `%s': %s", func, lua_tostring(L, -1));nres = -nres; //第一个返回值while (*sig) {switch (*sig++) { //"dd>d":>后面部分case 'd':if (!lua_isnumber(L, nres)) error(L, "wrong result type");*va_arg(vl, double *) = lua_tonumber(L, nres);break;case 'i': /* int result */if (!lua_isnumber(L, nres)) error(L, "wrong result type");*va_arg(vl, int *) = (int)lua_tonumber(L, nres);break;case 's': /* string result */if (!lua_isstring(L, nres)) error(L, "wrong result type");*va_arg(vl, const char **) = lua_tostring(L, nres);break;default:error(L, "invalid option (%c)", *(sig - 1));}nres++;}va_end(vl);}
四、Lua调用C函数
必须先注册函数,即把C函数地址以适当方式传递给Lua解释器,函数指针就可以啦。
注册的过程,其实就是C端通过栈把C函数的指针传给lua的环境变量中。lua_pushcfunction
实例
第一个例子,注册 sin函数给lua调用,最简单粗暴的注册。
// 这是所有注册的C函数的格式,// 函数指针格式,将指针压栈传给lua(function类型),lua即可定位到函数地址,进行调用。// int: 返回值个数,这个是要告诉Lua的,这样Lua才知道从栈中如何获取返回值。// L: lua当前环境,必须以这个值做第一个参数。typedef int (*lua_CFunction) (lua_State *L);//注册的C函数static int l_sin (lua_State *L) {double d = luaL_checknumber(L, 1);lua_pushnumber(L, sin(d));return 1; /* number of results */}lua_pushcfunction(l, l_sin); // 函数直接压栈,最简单粗暴的方式lua_setglobal(l, "mysin"); // lua中mysin = l_sin
第二个例子,注册一个目录展示的函数给lua,lua标准没有此类函数。
#include <dirent.h>#include <errno.h>static int l_dir (lua_State *L) {DIR *dir;struct dirent *entry;int i;const char *path = luaL_checkstring(L, 1);/* open directory */dir = opendir(path);if (dir == NULL) { /* error opening the directory? */lua_pushnil(L); /* return nil and ... */lua_pushstring(L, strerror(errno)); /* error message */return 2; /* number of results */}/* create result table */lua_newtable(L);i = 1;while ((entry = readdir(dir)) != NULL) {lua_pushnumber(L, i++); /* push key */lua_pushstring(L, entry->d_name); /* push value */lua_settable(L, -3);}closedir(dir);return 1; /* table is already on top */}
注册C函数库
设计一个lua函数库给C使用很简单,一个table搞定。
C可以借助辅助函数库lauxlib.h,批量设计C函数然后注册到一个lua table中。
以下是注册函数库名mylib的例子。mylib中有一个函数l_dir。
/*******************mylib.h********************/int luaopen_mylib (lua_State *L); //打开函数库方法(批量注册函数)//当解释器创建新的状态的时候会调用这个宏#define LUA_EXTRALIBS { "mylib", luaopen_mylib },
/*************库中的待注册函数*************/static int l_dir (lua_State *L) {...}/*************mylib.cpp*************/static const struct luaL_reg mylib [] = { //name-function数组,批量注册//两个元素的结构体。{"dir", l_dir}, //dir:函数名,l_dir:C函数指针{NULL, NULL} //必须以这个结尾,才能识别结束};int luaopen_mylib (lua_State *L) {luaL_openlib(L, //辅助函数帮助批量注册"mylib", //库名称,在lua的当前环境下创建或者reuse一个叫mylib的表mylib, //库函数数组,name-function0 //upvalue,);return 1; //传给lua,返回值个数,}/*******************Lua代码************************///1、以动态链接库的方式,由lua动态加载。//2、跟主C代码一起编译,在C代码逻辑中注册。mylib = loadlib("fullname-of-your-library", //链接库路径"luaopen_mylib" //打开库的函数)mylib() //打开库
写C函数技巧:数组操作
数组会被经常用于排序,这涉及大了高频次的访问数组元素,提供专门优化的API很有必要。
// index是负值索引时,lua_rawgeti(L,index,key)// 等价于// lua_pushnumber(L, key);// ua_rawget(L, index);void lua_rawgeti (lua_State *L, int index, int key);//index是负值索引时,lua_rawseti(L, index, key)// 等价于//lua_pushnumber(L, key);//lua_insert(L, -2); /* put 'key' below previous value *///lua_rawset(L, index);void lua_rawseti (lua_State *L, int index, int key);/*******************例子***********************///以数组的每一个元素为参数调用一个指定的函数,并将数组的该元素替换为调用函数返回的结果int l_map (lua_State *L) {int i, n;luaL_checktype(L, 1, LUA_TTABLE); //第一个参数必须是tableluaL_checktype(L, 2, LUA_TFUNCTION); //第二个参数必须是functionn = luaL_getn(L, 1); //数组大小for (i = 1; i <= n; i++) {lua_pushvalue(L, 2); /* push f */lua_rawgeti(L, 1, i); /* push t[i] */lua_call(L, 1, 1); /* call f(t[i]) */lua_rawseti(L, 1, i); /* t[i] = result */}return 0; /* no results */}
写C函数技巧:字符串处理
C字符串和lua字符串差异挺大的。之间传送字符容易出问题。
lua的字符串不可修改,lua的字符串给C时,C别想着去修改字符串,也不要将其出栈。
C给lua字符串时,C要处理好缓冲区分配释放。
少量字符串
//等价于lua的连接符..//适合少量字符串,类似table.concatlua_concat(L, n) //连接(同时会出栈)栈顶的n个值,并将最终结果放到栈顶。//格式化字符串的,类似sprintf,无需提供缓冲数组,lua自动管理,你尽管加的的来就是。适合少量字符串//%%(表示字符 '%')、//%s(用来格式化字符串)、//%d(格式化整数)、//%f(格式化Lua数字,即 double)和//%c(接受一个数字并将其作为字符),//不支持宽度和精度等选项const char *lua_pushfstring (lua_State *L, const char *fmt, ...);lua_pushfstring(L, "%%%d:%s:%f:%c",100,"100",100.0,124) //插入栈顶lua_pushlstring(L, s+i, j-i+1); //将字符串s从i到j位置(包含i和j)的子串传递给lua
例子:字符串分割
//在lua中,//split("hi,,there", ",")以,号分割字符串,并把子串保存在table中。static int l_split (lua_State *L) {const char *s = luaL_checkstring(L, 1);const char *sep = luaL_checkstring(L, 2);const char *e;int i = 1;lua_newtable(L); /* result *//* repeat for each separator */while ((e = strchr(s, *sep)) != NULL) {lua_pushlstring(L, s, e-s); /* push substring */lua_rawseti(L, -2, i++);s = e + 1; /* skip separator */}/* push last substring */lua_pushstring(L, s);lua_rawseti(L, -2, i);return 1; /* return the table */}
大量字符串
luaL_Buffer,类似I/O buffer,满了才flush。
同时用到了前面讲到的栈算法来连接多个buffer。
/**********lstrlib.c string.upper实现**************/static int str_upper (lua_State *L) {size_t l;size_t i;luaL_Buffer b;const char *s = luaL_checklstr(L, 1, &l);luaL_buffinit(L, &b); //初始化b,L状态的拷贝for (i=0; i<l; i++)luaL_putchar(&b, toupper((unsigned char)(s[i]))); //单个字符luaL_pushresult(&b); //刷新buffer并将最终字符串放到栈顶return 1;}
写C函数技巧:在C函数中保存状态
the registry:存储全局
C函数可以将非局部数据可以保存在lua的一个叫registry的表中,C代码可以自由使用,但Lua代码不能访问他。
registry 一直位于一个由LUA_REGISTRYINDEX定义的值所对应的假索引(pseudo-index)的位置。假索引意思就是可以通过访问栈这个位置来得到,但是他又不在栈中。但是注意,如果会改变栈的时候如lua_remove、lua_insert,不要用这个索引。
static const char Key = 'k'; //唯一key,用于解决registry可能存在的域冲突。//保存一个值到registry中lua_pushlightuserdata(L, (void *)&Key); //压栈一个C指针lua_pushnumber(L, myNumber); //压栈一个myNumberlua_settable(L, LUA_REGISTRYINDEX); //registry[&Key] = myNumber//从registry中中获取一个值lua_pushlightuserdata(L, (void *)&Key); //压栈一个C指针lua_gettable(L, LUA_REGISTRYINDEX); //压榨registry[&Key]myNumber = lua_tonumber(L, -1); //转成number
reference:解决key冲突
所有C库共享registry,这样可能引起域冲突。
方法一:借鉴lua中的{}作为唯一key,C中可以用一个static变量地址来充当registry,C链接器会保证这个地址是惟一的。
方法二:universal unique identifier(uuid),很多系统都有专门的程序来产生这种标示符(比如linux下的uuidgen)。一个uuid是一个由本机IP地址、时间戳、和一个随机内容组合起来的128位的数字(以16进制的方式书写,用来形成一个字符串)。保证每个uuid都是唯一的。
不要用数字作为key,lua的Reference系统已经占用了数字,用于提供给C一个唯一数值,这个值一般用来当做指针使用,方便C去引用lua的变量,使用场景如下。请记住,不要想着通过C指针来引用lua对象,这不是Lua的设计思想。
//想将栈中的值保存到registry中,但又没好的方法找唯一域的时候int r = luaL_ref(L, LUA_REGISTRYINDEX); //将数据保存到registry中,//栈顶弹出一个元素并保存到registry中,//并返回指向它的数值lua_rawgeti(L, LUA_REGISTRYINDEX, r); //registry表中获取r对应的值并压栈luaL_unref(L, LUA_REGISTRYINDEX, r); //解除引用//如果要保存到registry的数据是nil时,Reference是常量LUA_REFNILluaL_unref(L, LUA_REGISTRYINDEX, LUA_REFNIL); //无效lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_REFNIL); //有效,nil压栈//Reference常量LUA_NOREF:一个表示任何非有效的reference的整数值//用来判断reference是否有效。
upvalues:实现static
比如C端实现一个统计调用次数的函数给lua调用,常规的用static,最好的办法用lua的upvalue代替
//闭包函数static int counter (lua_State *L){//第一个upvalue的假索引,何为假索引见前面解释double val = lua_tonumber(L, lua_upvalueindex(1));lua_pushnumber(L, ++val); /* new value */lua_pushvalue(L, -1); //拷贝一份压栈lua_replace(L, lua_upvalueindex(1)); //用拷贝的那一份替换加索引的return 1; //返回new value}int newCounter (lua_State *L) {lua_pushnumber(L, 0);lua_pushcclosure(L, &counter, 1); //1个upvaluereturn 1;}
五、Lua调用C类:UserDefined Types
一个userdata值提供了一个在Lua中没有预定义操作的raw内存区域。
把userdata看成一块内存。
Full Userdata
注册一个数组类型给Lua
原始方法
最基础原始的做法,见下面代码,有一个重大安全隐患,userdata是一块内存区域,必须要先确保类型匹配才不会导致内存越界操作。
------------在lua中的使用方式如下a = array.new(1000)print(a) --> userdata: 0x8064d48print(array.size(a)) --> 1000for i=1,1000 doarray.set(a, i, 1/i)endprint(array.get(a, 10)) --> 0.1
typedef struct NumArray { //声明一个数组结构int size;double values[1]; //1是占位符,就是一个double} NumArray;//按照指定的大小分配一块内存,将对应的userdata放到栈内,并返回内存块的地址。void *lua_newuserdata (lua_State *L, size_t size);//a = array.new(1000)static int newarray (lua_State *L) {int n = luaL_checkint(L, 1); //luaL_checkint是用来检查整数的luaL_checknumber的变体size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double); //有n个NumArray的数组的字节数NumArray *a = (NumArray *)lua_newuserdata(L, nbytes); //分配内存,已经在栈顶。a->size = n;return 1;}//array.set(array, index, value)static int setarray (lua_State *L) {//重大漏洞隐患,没有安全防护,不知道内存地址是否有效//加入在lua中array.set(io.stdin, 1, 0)。//程序运行的结果可能导致内存core dumpNumArray *a = (NumArray *)lua_touserdata(L, 1);int index = luaL_checkint(L, 2);double value = luaL_checknumber(L, 3);// lua中这样调用才会保存// array.set(a, 11, 0) --> stdin:1: bad argument #1 to 'set' ('array' expected)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;}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 int getsize (lua_State *L) {NumArray *a = (NumArray *)lua_touserdata(L, 1);luaL_argcheck(L, a != NULL, 1, "`array' expected");lua_pushnumber(L, a->size);return 1;}//这是注册库的代码static const struct luaL_reg arraylib [] = {{"new", newarray},{"set", setarray},{"get", getarray},{"size", getsize},{NULL, NULL}};int luaopen_array (lua_State *L) {luaL_openlib(L, "array", arraylib, 0);return 1;}
在Linux中,一个有100K元素的数组大概占用800KB的内存,同样的条件由Lua 表实现的数组需要1.5MB的内存。
改进1:修补userdata内存漏洞
解决上面的安全漏洞隐患。解决思路就是为userdata设置一个metatable,通过检查metatable来确定userdata是否是正确的userdata
array.get(io.stdin, 10) -- error: bad argument #1 to 'getarray' ('array' expected)
//创建一个新表(将用作metatable),将新表放到栈顶并建立表和registry中类型名的联系。//这个关联是双向的:使用类型名作为表的key;同时使用表作为类型名的keyint luaL_newmetatable (lua_State *L, const char *tname);//获取registry中的tname对应的metatablevoid luaL_getmetatable (lua_State *L, const char *tname);//检查在栈中指定位置的对象是否为带有给定名字的metatable的userdata,错误返回NULL,成功返回userata地址void *luaL_checkudata (lua_State *L, int index, const char *tname);//注册库,上面已写。int luaopen_array (lua_State *L) {//名为LuaBook.array的metable压栈,保存在registry中,类型名做索引luaL_newmetatable(L, "LuaBook.array");luaL_openlib(L, "array", arraylib, 0);return 1;}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);luaL_getmetatable(L, "LuaBook.array");lua_setmetatable(L, -2); //为userdata设置metatablea->size = n;return 1; /* new userdatum is already on the stack */}static NumArray *checkarray (lua_State *L) {void *ud = luaL_checkudata(L, 1, "LuaBook.array"); //检验userdata类型luaL_argcheck(L, ud != NULL, 1, "`array' expected");return (NumArray *)ud;}static int getsize (lua_State *L) {NumArray *a = checkarray(L);lua_pushnumber(L, a->size);return 1;}static int setarray (lua_State *L) {double newvalue = luaL_checknumber(L, 3);*getelem(L) = newvalue;return 0;}static int getarray (lua_State *L) {lua_pushnumber(L, *getelem(L));return 1;}//获取指定索引的元素static double *getelem (lua_State *L) {NumArray *a = checkarray(L);int index = luaL_checkint(L, 2);luaL_argcheck(L, 1 <= index && index <= a->size, 2,"index out of range");/* return element address */return &a->values[index - 1];}
改进2:类的方式访问
要能够像下面这样,是对象的方式操作(:冒号)。
a = array.new(1000)print(a:size()) --> 1000a:set(10, 3.4)print(a:get(10)) --> 3.4--------------思路----------------在lua可以直接如下设置。在C中可以完全替代这些lua工作local metaarray = getmetatable(array.new(1))metaarray.__index = metaarraymetaarray.set = array.setmetaarray.get = array.getmetaarray.size = array.size
//getsize、getarray和setarray的实现前面已有static const struct luaL_reg arraylib_f [] = {{"new", newarray},{NULL, NULL}};static const struct luaL_reg arraylib_m [] = {{"__tostring", array2string},{"set", setarray},{"get", getarray},{"size", getsize},{NULL, NULL}};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,注册的函数没有在table中,在lua中直接使用函数luaL_openlib(L, NULL, arraylib_m, 0);luaL_openlib(L, "array", arraylib_f, 0);return 1;}int array2string (lua_State *L) {NumArray *a = checkarray(L);lua_pushfstring(L, "array(%d)", a->size);return 1;}
改进2:数组的方式访问
a = array.new(1000)a[10] = 3.4 -- setarrayprint(a[10]) -- getarray --> 3.4--------------思路----------------在lua可以直接如下设置。在C中可以完全替代这些lua工作local metaarray = getmetatable(newarray(1))metaarray.__index = array.getmetaarray.__newindex = array.set
//代替以上的lua思路int luaopen_array (lua_State *L) {luaL_newmetatable(L, "LuaBook.array");luaL_openlib(L, "array", arraylib, 0);/* now the stack has the metatable at index 1 and'array' at index 2 */lua_pushstring(L, "__index");lua_pushstring(L, "get");lua_gettable(L, 2); /* get array.get */lua_settable(L, 1); /* metatable.__index = array.get */lua_pushstring(L, "__newindex");lua_pushstring(L, "set");lua_gettable(L, 2); /* get array.set */lua_settable(L, 1); /* metatable.__newindex = array.set */return 0;}
Light Userdata
light userdata是一个表示C指针的值(也就是一个void 类型的值),没有metatable。由于它是一个值,我们不能创建它们,像数字一样,不需要垃圾回收管理。
full userdata,表示一块内存区域。
light userdata,void指针,可以指向任何类型,也就可以是light userdata指向full userdata。
//将一个light userdatum直接入栈void lua_pushlightuserdata (lua_State *L, void *p);
