分下面几种函数:

  • 读写Lua全局变量的函数
  • 调用Lua函数的函数
  • 运行Lua代码片断的函数
  • 注册C函数然后可以在Lua中被调用的函数

    一、一个简单的Lua解释器

    ```c

    include

    include // Lua提供的基础函数

    include // 辅助库(auxlib)提供的函数,都是luaL_开头,是lua.h的基础上的封装(抽象)

    include // 定义了打开各种库的api

int main (void) { char buff[256]; int error;

  1. // 创建一个新的Lua环境environment(全局表),几乎是空的,连print都没有
  2. // 通过lualib.h的接口,打开各种库,注册到环境table中。
  3. lua_State *L = lua_open();
  4. luaopen_base(L); // 基础库
  5. luaopen_table(L); // table库,这样lua中才能使用table
  6. luaopen_io(L); // io库,lua中才能使用io.write之类的
  7. luaopen_string(L); // string库
  8. luaopen_math(L); // math库
  9. while (fgets(buff, sizeof(buff), stdin) != NULL) {
  10. error = luaL_loadbuffer(L, buff, strlen(buff), // 遍历lua代码类似loadstring
  11. // 成功,就把chunk压栈
  12. // 错误,就把错误消息压栈
  13. "line") || lua_pcall(L, 0, 0, 0); // 弹出栈顶lua chunk并执行(保护模式)
  14. if (error) {
  15. fprintf(stderr, "%s", lua_tostring(L, -1)); // 栈顶数据转成lua string
  16. lua_pop(L, 1); // 弹出栈顶错误消息
  17. }
  18. }
  19. lua_close(L);
  20. return 0;

}

  1. <a name="ZOSoK"></a>
  2. # 二、C和Lua交互栈
  3. 为什么会用到栈?Lua和C之间交互会出现的问题。<br />1、变量类型匹配问题<br />Lua是动态类型,每个变量的类型运行时会改变。C肯定不能这样,定义了就固定了。<br />2、内存管理问题<br />Lua是有自动内存管理,C堆内存非自动,可能Lua那边已经回收,C这边还在。<br />通过栈来保存Lua值,栈由Lua管理,C通过调用Lua来将C类型值压栈,然后出栈数据给Lua使用。Lua以LIFO原则来操作栈,也就是说只改变栈顶函数。
  4. <a name="dOzGt"></a>
  5. ## 操作栈:压栈、查询
  6. API通过索引来访问栈元素,索引规则:<br />正数表示法:1表示第一个压栈元素,也就是**栈底**,2是往上一个。<br />负数表示法:-1表示最后一个压栈元素,也就是**栈顶**,-2是往下一个。
  7. ```c
  8. void lua_pushnil (lua_State *L);
  9. void lua_pushboolean (lua_State *L, int bool);
  10. void lua_pushnumber (lua_State *L, double n);
  11. //lua字符串不是以0作为结尾,而必须指明长度。
  12. //lua对栈中的字符串,只拷贝,不会以指针来操作。
  13. void lua_pushlstring (lua_State *L, const char *s, size_t length);
  14. void lua_pushstring (lua_State *L, const char *s);
  15. //检查栈是否有能存储sz条记录的空间
  16. //lua.h中定义了栈最小空闲空间LUA_MINSTACK
  17. int lua_checkstack (lua_State *L, int sz);
  18. //index位置的栈元素是否是目标类型。如lua_isnumber,lua_isstring,lua_istable
  19. //lua_isnumber和lua_isstring通过是否能转换来判断。
  20. //对下面lua_type的封装
  21. int lua_is... (lua_State *L, int index);
  22. //栈顶元素是否是type类型的lua值
  23. //enum_type值如下
  24. //LUA_TNIL, LUA_TBOOLEAN, LUA_TNUMBER, LUA_TSTRING
  25. //LUA_TTABLE, LUA_TFUNCTION, LUA_TUSERDATA, LUA_TTHREAD
  26. int lua_type (lua_State *L, enum_type type);
  27. //从栈中获取元素,注意没有弹栈
  28. int lua_toboolean (lua_State *L, int index); //失败返回0
  29. double lua_tonumber (lua_State *L, int index); //失败返回0
  30. const char * lua_tostring (lua_State *L, int index); //失败返回0
  31. size_t lua_strlen (lua_State *L, int index); //失败返回NULL
  32. //lua字符串不是0结尾,而C是,所以C操作从栈中返回的string要特别注意
  33. const char *s = lua_tostring(L, -1); //获取luastring
  34. size_t len_lua = lua_strlen(L, -1); //s的真实长度
  35. size_t len_c = strlen(s); //c以字符串操作s这是不对的,s中间可能有0字符,
  36. //len_c <= len_lua
  37. int lua_gettop (lua_State *L); //返回栈元素个数,栈顶整数索引
  38. void lua_settop (lua_State *L, int index); //超过index的被丢失,不足的填nil,index=0清空栈
  39. #define lua_pop(L,n) lua_settop(L, -(n)-1) //丢失栈顶的n个元素
  40. void lua_pushvalue (lua_State *L, int index); //拷贝index元素,压入栈顶。
  41. void lua_remove (lua_State *L, int index); //移除index元素
  42. void lua_insert (lua_State *L, int index); //栈顶元素插入到index位置,上面的上移。
  43. void lua_replace (lua_State *L, int index); //弹出栈顶,并替换index位置的值。

打印栈

  1. // 从栈底到栈顶遍历了整个堆栈,依照每个元素自己的类型打印出其值。它用引号输出字符串;
  2. // 以%g的格式输出数字;对于其它值(table,函数,等等)
  3. // 它仅仅输出它们的类型(lua_typename转换一个类型码到类型名)
  4. static void stackDump (lua_State *L) {
  5. int i;
  6. int top = lua_gettop(L);
  7. for (i = 1; i <= top; i++) { /* repeat for each level */
  8. int t = lua_type(L, i);
  9. switch (t) {
  10. case LUA_TSTRING: /* strings */
  11. printf("`%s'", lua_tostring(L, i));
  12. break;
  13. case LUA_TBOOLEAN: /* booleans */
  14. printf(lua_toboolean(L, i) ? "true" : "false");
  15. break;
  16. case LUA_TNUMBER: /* numbers */
  17. printf("%g", lua_tonumber(L, i));
  18. break;
  19. default: /* other values */
  20. printf("%s", lua_typename(L, t));
  21. break;
  22. }
  23. printf(" "); /* put a separator */
  24. }
  25. printf("\n"); /* end the listing */
  26. }

三、C调用Lua

C调用Lua原理比较简单。
1、由lua端设计了栈,并给C提供了栈操作的API
2、通过API,C可以直接将Lua当前环境下的变量入栈。
3、也就很容易让函数、变量压栈,并执行函数,返回值、错误信息再压栈,供C端使用。

C使用Lua配置

场景:C是一个窗口程序,用Lua做颜色主题的配置文件。
具体方法:Lua是用一个全局表做配置文件,C封装直接获取/设置表的域值的函数。

  1. ------------颜色主题配置文件------------
  2. WHITE = { r = 1, g = 1, b = 1}
  3. RED = { r = 1, g = 0, b = 0}
  4. background = WHITE
  1. /**********C中预定义的颜色主题配置************/
  2. #define MAX_COLOR 255
  3. struct ColorTable {
  4. char *name;
  5. unsigned char red, green, blue;
  6. } colortable[] = {
  7. {"WHITE", MAX_COLOR, MAX_COLOR, MAX_COLOR},
  8. {"RED", MAX_COLOR, 0, 0},
  9. {"GREEN", 0, MAX_COLOR, 0},
  10. {"BLUE", 0, 0, MAX_COLOR},
  11. {"BLACK", 0, 0, 0},
  12. ...
  13. {NULL, 0, 0, 0}
  14. };
  15. /******************************************/
  16. /*********C封装backgroud[key]操作********/
  17. //假设当前栈顶元素为backgroud
  18. int getfield (const char *key) { //假设栈顶已经是backgroud
  19. int result;
  20. lua_pushstring(L, key); //key压栈
  21. lua_gettable(L, -2); //key出栈,background[key]压栈
  22. if (!lua_isnumber(L, -1)) //检查数值合法性
  23. error(L, "invalid component in background color");
  24. result = (int)lua_tonumber(L, -1) * MAX_COLOR;
  25. lua_pop(L, 1); //栈顶弹出
  26. return result;
  27. }
  28. /*********C封装backgroud[key] = value操作********/
  29. //假设栈顶是background
  30. void setfield (const char *index, int value) {
  31. lua_pushstring(L, index);
  32. lua_pushnumber(L, (double)value/MAX_COLOR);
  33. lua_settable(L, -3); //栈顶是value,栈顶-1是key,栈顶-2是background
  34. //background[key]=value,key,value弹栈。
  35. }
  36. /*********C封装backgroud = {r=1,g=2,b=3}操作********/
  37. //假设栈顶是background
  38. void setcolor (struct ColorTable *ct) {
  39. lua_newtable(L); //创建表table
  40. setfield("r", ct->red); /* table.r = ct->r */
  41. setfield("g", ct->green); /* table.g = ct->g */
  42. setfield("b", ct->blue); /* table.b = ct->b */
  43. lua_setglobal(ct->name); /* 'name' = table */
  44. }
  45. /********************封装函数使用***********************/
  46. /*********把上面C中预定义的常见颜色值加载到lua全局表_G中**********/
  47. int i = 0;
  48. while (colortable[i].name != NULL)
  49. setcolor(&colortable[i++]);
  50. /*********C中获取上面lua配置中的background表的r、g、b值**********/
  51. lua_getglobal(L, "background"); //全局变量background入栈
  52. if (!lua_istable(L, -1)) //检查结果
  53. error(L, "`background' is not a valid color table");
  54. red = getfield("r"); //backgroud.r
  55. green = getfield("g"); //backgroud.g
  56. blue = getfield("b"); //backgroud.b

C调用Lua函数

场景:C写王者荣耀,Lua计算伤害数值。

  1. --------------Lua函数
  2. function f (x, y)
  3. return (x^2 * math.sin(y))/(1 - x)
  4. end
  1. /* 调用Lua的f函数 */
  2. double f (double x, double y) {
  3. double z;
  4. lua_getglobal(L, "f"); //全部变量f(函数)压栈
  5. lua_pushnumber(L, x); //参数x压栈
  6. lua_pushnumber(L, y); //参数y压栈
  7. //多使用pcall,安全。还有错误处理函数
  8. if (lua_pcall(L, 2, 1, 0) != 0) //调用栈中函数,2个参数,1个返回值,没有错误处理函数
  9. //返回值,错误信息入栈
  10. error(L, "error running function `f': %s",lua_tostring(L, -1)); //输出错误信息
  11. //检查返回值数据类型
  12. if (!lua_isnumber(L, -1)) error(L, "function `f' must return a number");
  13. z = lua_tonumber(L, -1); //获取返回值
  14. lua_pop(L, 1); //返回值出栈
  15. return z;
  16. }

C动态调用Lua函数

动态调用lua函数,函数名,参数类型,参数数量,返回值都运行时决定。

  1. //local ret = f(x,y)
  2. //dd>d:double f(double, double)
  3. call_va("f", "dd>d", x, y, &z);
  4. #include <stdarg.h>
  5. void call_va (const char *func, const char *sig, ...) {
  6. va_list vl;
  7. int narg, nres; //参数数量,返回值数量
  8. va_start(vl, sig);
  9. lua_getglobal(L, func); //全局函数func
  10. narg = 0;
  11. //根据sig字符串,参数入栈。
  12. while (*sig) {
  13. switch (*sig++) { //"dd>d":>前面部分
  14. case 'd':
  15. lua_pushnumber(L, va_arg(vl, double));
  16. break;
  17. case 'i':
  18. lua_pushnumber(L, va_arg(vl, int));
  19. break;
  20. case 's':
  21. lua_pushstring(L, va_arg(vl, char *));
  22. break;
  23. case '>':
  24. goto endwhile;
  25. default:
  26. error(L, "invalid option (%c)", *(sig - 1));
  27. }
  28. narg++;
  29. luaL_checkstack(L, 1, "too many arguments"); //检查栈空间
  30. } endwhile:
  31. nres = strlen(sig); //检查返回值数量与sig字符串标识的是否对得上
  32. if (lua_pcall(L, narg, nres, 0) != 0) //调用函数
  33. error(L, "error running function `%s': %s", func, lua_tostring(L, -1));
  34. nres = -nres; //第一个返回值
  35. while (*sig) {
  36. switch (*sig++) { //"dd>d":>后面部分
  37. case 'd':
  38. if (!lua_isnumber(L, nres)) error(L, "wrong result type");
  39. *va_arg(vl, double *) = lua_tonumber(L, nres);
  40. break;
  41. case 'i': /* int result */
  42. if (!lua_isnumber(L, nres)) error(L, "wrong result type");
  43. *va_arg(vl, int *) = (int)lua_tonumber(L, nres);
  44. break;
  45. case 's': /* string result */
  46. if (!lua_isstring(L, nres)) error(L, "wrong result type");
  47. *va_arg(vl, const char **) = lua_tostring(L, nres);
  48. break;
  49. default:
  50. error(L, "invalid option (%c)", *(sig - 1));
  51. }
  52. nres++;
  53. }
  54. va_end(vl);
  55. }

四、Lua调用C函数

必须先注册函数,即把C函数地址以适当方式传递给Lua解释器,函数指针就可以啦。
注册的过程,其实就是C端通过栈把C函数的指针传给lua的环境变量中。lua_pushcfunction

实例

第一个例子,注册 sin函数给lua调用,最简单粗暴的注册。

  1. // 这是所有注册的C函数的格式,
  2. // 函数指针格式,将指针压栈传给lua(function类型),lua即可定位到函数地址,进行调用。
  3. // int: 返回值个数,这个是要告诉Lua的,这样Lua才知道从栈中如何获取返回值。
  4. // L: lua当前环境,必须以这个值做第一个参数。
  5. typedef int (*lua_CFunction) (lua_State *L);
  6. //注册的C函数
  7. static int l_sin (lua_State *L) {
  8. double d = luaL_checknumber(L, 1);
  9. lua_pushnumber(L, sin(d));
  10. return 1; /* number of results */
  11. }
  12. lua_pushcfunction(l, l_sin); // 函数直接压栈,最简单粗暴的方式
  13. lua_setglobal(l, "mysin"); // lua中mysin = l_sin

第二个例子,注册一个目录展示的函数给lua,lua标准没有此类函数。

  1. #include <dirent.h>
  2. #include <errno.h>
  3. static int l_dir (lua_State *L) {
  4. DIR *dir;
  5. struct dirent *entry;
  6. int i;
  7. const char *path = luaL_checkstring(L, 1);
  8. /* open directory */
  9. dir = opendir(path);
  10. if (dir == NULL) { /* error opening the directory? */
  11. lua_pushnil(L); /* return nil and ... */
  12. lua_pushstring(L, strerror(errno)); /* error message */
  13. return 2; /* number of results */
  14. }
  15. /* create result table */
  16. lua_newtable(L);
  17. i = 1;
  18. while ((entry = readdir(dir)) != NULL) {
  19. lua_pushnumber(L, i++); /* push key */
  20. lua_pushstring(L, entry->d_name); /* push value */
  21. lua_settable(L, -3);
  22. }
  23. closedir(dir);
  24. return 1; /* table is already on top */
  25. }

注册C函数库

设计一个lua函数库给C使用很简单,一个table搞定。
C可以借助辅助函数库lauxlib.h,批量设计C函数然后注册到一个lua table中。

以下是注册函数库名mylib的例子。mylib中有一个函数l_dir。

  1. /*******************mylib.h********************/
  2. int luaopen_mylib (lua_State *L); //打开函数库方法(批量注册函数)
  3. //当解释器创建新的状态的时候会调用这个宏
  4. #define LUA_EXTRALIBS { "mylib", luaopen_mylib },
  1. /*************库中的待注册函数*************/
  2. static int l_dir (lua_State *L) {
  3. ...
  4. }
  5. /*************mylib.cpp*************/
  6. static const struct luaL_reg mylib [] = { //name-function数组,批量注册
  7. //两个元素的结构体。
  8. {"dir", l_dir}, //dir:函数名,l_dir:C函数指针
  9. {NULL, NULL} //必须以这个结尾,才能识别结束
  10. };
  11. int luaopen_mylib (lua_State *L) {
  12. luaL_openlib(L, //辅助函数帮助批量注册
  13. "mylib", //库名称,在lua的当前环境下创建或者reuse一个叫mylib的表
  14. mylib, //库函数数组,name-function
  15. 0 //upvalue,
  16. );
  17. return 1; //传给lua,返回值个数,
  18. }
  19. /*******************Lua代码************************/
  20. //1、以动态链接库的方式,由lua动态加载。
  21. //2、跟主C代码一起编译,在C代码逻辑中注册。
  22. mylib = loadlib("fullname-of-your-library", //链接库路径
  23. "luaopen_mylib" //打开库的函数
  24. )
  25. mylib() //打开库

写C函数技巧:数组操作

数组会被经常用于排序,这涉及大了高频次的访问数组元素,提供专门优化的API很有必要。

  1. // index是负值索引时,lua_rawgeti(L,index,key)
  2. // 等价于
  3. // lua_pushnumber(L, key);
  4. // ua_rawget(L, index);
  5. void lua_rawgeti (lua_State *L, int index, int key);
  6. //index是负值索引时,lua_rawseti(L, index, key)
  7. // 等价于
  8. //lua_pushnumber(L, key);
  9. //lua_insert(L, -2); /* put 'key' below previous value */
  10. //lua_rawset(L, index);
  11. void lua_rawseti (lua_State *L, int index, int key);
  12. /*******************例子***********************/
  13. //以数组的每一个元素为参数调用一个指定的函数,并将数组的该元素替换为调用函数返回的结果
  14. int l_map (lua_State *L) {
  15. int i, n;
  16. luaL_checktype(L, 1, LUA_TTABLE); //第一个参数必须是table
  17. luaL_checktype(L, 2, LUA_TFUNCTION); //第二个参数必须是function
  18. n = luaL_getn(L, 1); //数组大小
  19. for (i = 1; i <= n; i++) {
  20. lua_pushvalue(L, 2); /* push f */
  21. lua_rawgeti(L, 1, i); /* push t[i] */
  22. lua_call(L, 1, 1); /* call f(t[i]) */
  23. lua_rawseti(L, 1, i); /* t[i] = result */
  24. }
  25. return 0; /* no results */
  26. }

写C函数技巧:字符串处理

C字符串和lua字符串差异挺大的。之间传送字符容易出问题。
lua的字符串不可修改,lua的字符串给C时,C别想着去修改字符串,也不要将其出栈。
C给lua字符串时,C要处理好缓冲区分配释放。

少量字符串

  1. //等价于lua的连接符..
  2. //适合少量字符串,类似table.concat
  3. lua_concat(L, n) //连接(同时会出栈)栈顶的n个值,并将最终结果放到栈顶。
  4. //格式化字符串的,类似sprintf,无需提供缓冲数组,lua自动管理,你尽管加的的来就是。适合少量字符串
  5. //%%(表示字符 '%')、
  6. //%s(用来格式化字符串)、
  7. //%d(格式化整数)、
  8. //%f(格式化Lua数字,即 double)和
  9. //%c(接受一个数字并将其作为字符),
  10. //不支持宽度和精度等选项
  11. const char *lua_pushfstring (lua_State *L, const char *fmt, ...);
  12. lua_pushfstring(L, "%%%d:%s:%f:%c",100,"100",100.0,124) //插入栈顶
  13. lua_pushlstring(L, s+i, j-i+1); //将字符串s从i到j位置(包含i和j)的子串传递给lua

例子:字符串分割

  1. //在lua中,
  2. //split("hi,,there", ",")以,号分割字符串,并把子串保存在table中。
  3. static int l_split (lua_State *L) {
  4. const char *s = luaL_checkstring(L, 1);
  5. const char *sep = luaL_checkstring(L, 2);
  6. const char *e;
  7. int i = 1;
  8. lua_newtable(L); /* result */
  9. /* repeat for each separator */
  10. while ((e = strchr(s, *sep)) != NULL) {
  11. lua_pushlstring(L, s, e-s); /* push substring */
  12. lua_rawseti(L, -2, i++);
  13. s = e + 1; /* skip separator */
  14. }
  15. /* push last substring */
  16. lua_pushstring(L, s);
  17. lua_rawseti(L, -2, i);
  18. return 1; /* return the table */
  19. }

大量字符串

luaL_Buffer,类似I/O buffer,满了才flush。
同时用到了前面讲到的栈算法来连接多个buffer。

  1. /**********lstrlib.c string.upper实现**************/
  2. static int str_upper (lua_State *L) {
  3. size_t l;
  4. size_t i;
  5. luaL_Buffer b;
  6. const char *s = luaL_checklstr(L, 1, &l);
  7. luaL_buffinit(L, &b); //初始化b,L状态的拷贝
  8. for (i=0; i<l; i++)
  9. luaL_putchar(&b, toupper((unsigned char)(s[i]))); //单个字符
  10. luaL_pushresult(&b); //刷新buffer并将最终字符串放到栈顶
  11. return 1;
  12. }

写C函数技巧:在C函数中保存状态

the registry:存储全局

C函数可以将非局部数据可以保存在lua的一个叫registry的表中,C代码可以自由使用,但Lua代码不能访问他。
registry 一直位于一个由LUA_REGISTRYINDEX定义的值所对应的假索引(pseudo-index)的位置。假索引意思就是可以通过访问栈这个位置来得到,但是他又不在栈中。但是注意,如果会改变栈的时候如lua_remove、lua_insert,不要用这个索引。

  1. static const char Key = 'k'; //唯一key,用于解决registry可能存在的域冲突。
  2. //保存一个值到registry中
  3. lua_pushlightuserdata(L, (void *)&Key); //压栈一个C指针
  4. lua_pushnumber(L, myNumber); //压栈一个myNumber
  5. lua_settable(L, LUA_REGISTRYINDEX); //registry[&Key] = myNumber
  6. //从registry中中获取一个值
  7. lua_pushlightuserdata(L, (void *)&Key); //压栈一个C指针
  8. lua_gettable(L, LUA_REGISTRYINDEX); //压榨registry[&Key]
  9. 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的设计思想。

  1. //想将栈中的值保存到registry中,但又没好的方法找唯一域的时候
  2. int r = luaL_ref(L, LUA_REGISTRYINDEX); //将数据保存到registry中,
  3. //栈顶弹出一个元素并保存到registry中,
  4. //并返回指向它的数值
  5. lua_rawgeti(L, LUA_REGISTRYINDEX, r); //registry表中获取r对应的值并压栈
  6. luaL_unref(L, LUA_REGISTRYINDEX, r); //解除引用
  7. //如果要保存到registry的数据是nil时,Reference是常量LUA_REFNIL
  8. luaL_unref(L, LUA_REGISTRYINDEX, LUA_REFNIL); //无效
  9. lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_REFNIL); //有效,nil压栈
  10. //Reference常量LUA_NOREF:一个表示任何非有效的reference的整数值
  11. //用来判断reference是否有效。

upvalues:实现static

比如C端实现一个统计调用次数的函数给lua调用,常规的用static,最好的办法用lua的upvalue代替

  1. //闭包函数
  2. static int counter (lua_State *L){
  3. //第一个upvalue的假索引,何为假索引见前面解释
  4. double val = lua_tonumber(L, lua_upvalueindex(1));
  5. lua_pushnumber(L, ++val); /* new value */
  6. lua_pushvalue(L, -1); //拷贝一份压栈
  7. lua_replace(L, lua_upvalueindex(1)); //用拷贝的那一份替换加索引的
  8. return 1; //返回new value
  9. }
  10. int newCounter (lua_State *L) {
  11. lua_pushnumber(L, 0);
  12. lua_pushcclosure(L, &counter, 1); //1个upvalue
  13. return 1;
  14. }

五、Lua调用C类:UserDefined Types

一个userdata值提供了一个在Lua中没有预定义操作的raw内存区域。
把userdata看成一块内存。

Full Userdata

可以看成自己分配的一块内存(缓冲区)。

注册一个数组类型给Lua

原始方法

最基础原始的做法,见下面代码,有一个重大安全隐患,userdata是一块内存区域,必须要先确保类型匹配才不会导致内存越界操作。

  1. ------------在lua中的使用方式如下
  2. a = array.new(1000)
  3. print(a) --> userdata: 0x8064d48
  4. print(array.size(a)) --> 1000
  5. for i=1,1000 do
  6. array.set(a, i, 1/i)
  7. end
  8. print(array.get(a, 10)) --> 0.1
  1. typedef struct NumArray { //声明一个数组结构
  2. int size;
  3. double values[1]; //1是占位符,就是一个double
  4. } NumArray;
  5. //按照指定的大小分配一块内存,将对应的userdata放到栈内,并返回内存块的地址。
  6. void *lua_newuserdata (lua_State *L, size_t size);
  7. //a = array.new(1000)
  8. static int newarray (lua_State *L) {
  9. int n = luaL_checkint(L, 1); //luaL_checkint是用来检查整数的luaL_checknumber的变体
  10. size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double); //有n个NumArray的数组的字节数
  11. NumArray *a = (NumArray *)lua_newuserdata(L, nbytes); //分配内存,已经在栈顶。
  12. a->size = n;
  13. return 1;
  14. }
  15. //array.set(array, index, value)
  16. static int setarray (lua_State *L) {
  17. //重大漏洞隐患,没有安全防护,不知道内存地址是否有效
  18. //加入在lua中array.set(io.stdin, 1, 0)。
  19. //程序运行的结果可能导致内存core dump
  20. NumArray *a = (NumArray *)lua_touserdata(L, 1);
  21. int index = luaL_checkint(L, 2);
  22. double value = luaL_checknumber(L, 3);
  23. // lua中这样调用才会保存
  24. // array.set(a, 11, 0) --> stdin:1: bad argument #1 to 'set' ('array' expected)
  25. luaL_argcheck(L, a != NULL, 1, "`array' expected");
  26. luaL_argcheck(L, 1 <= index && index <= a->size, 2, "index out of range");
  27. a->values[index-1] = value;
  28. return 0;
  29. }
  30. static int getarray (lua_State *L) {
  31. NumArray *a = (NumArray *)lua_touserdata(L, 1);
  32. int index = luaL_checkint(L, 2);
  33. luaL_argcheck(L, a != NULL, 1, "'array' expected");
  34. luaL_argcheck(L, 1 <= index && index <= a->size, 2,
  35. "index out of range");
  36. lua_pushnumber(L, a->values[index-1]);
  37. return 1;
  38. }
  39. static int getsize (lua_State *L) {
  40. NumArray *a = (NumArray *)lua_touserdata(L, 1);
  41. luaL_argcheck(L, a != NULL, 1, "`array' expected");
  42. lua_pushnumber(L, a->size);
  43. return 1;
  44. }
  45. //这是注册库的代码
  46. static const struct luaL_reg arraylib [] = {
  47. {"new", newarray},
  48. {"set", setarray},
  49. {"get", getarray},
  50. {"size", getsize},
  51. {NULL, NULL}
  52. };
  53. int luaopen_array (lua_State *L) {
  54. luaL_openlib(L, "array", arraylib, 0);
  55. return 1;
  56. }

在Linux中,一个有100K元素的数组大概占用800KB的内存,同样的条件由Lua 表实现的数组需要1.5MB的内存。

改进1:修补userdata内存漏洞

解决上面的安全漏洞隐患。解决思路就是为userdata设置一个metatable,通过检查metatable来确定userdata是否是正确的userdata

  1. array.get(io.stdin, 10) -- error: bad argument #1 to 'getarray' ('array' expected)
  1. //创建一个新表(将用作metatable),将新表放到栈顶并建立表和registry中类型名的联系。
  2. //这个关联是双向的:使用类型名作为表的key;同时使用表作为类型名的key
  3. int luaL_newmetatable (lua_State *L, const char *tname);
  4. //获取registry中的tname对应的metatable
  5. void luaL_getmetatable (lua_State *L, const char *tname);
  6. //检查在栈中指定位置的对象是否为带有给定名字的metatable的userdata,错误返回NULL,成功返回userata地址
  7. void *luaL_checkudata (lua_State *L, int index, const char *tname);
  8. //注册库,上面已写。
  9. int luaopen_array (lua_State *L) {
  10. //名为LuaBook.array的metable压栈,保存在registry中,类型名做索引
  11. luaL_newmetatable(L, "LuaBook.array");
  12. luaL_openlib(L, "array", arraylib, 0);
  13. return 1;
  14. }
  15. static int newarray (lua_State *L) {
  16. int n = luaL_checkint(L, 1);
  17. size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double);
  18. NumArray *a = (NumArray *)lua_newuserdata(L, nbytes);
  19. luaL_getmetatable(L, "LuaBook.array");
  20. lua_setmetatable(L, -2); //为userdata设置metatable
  21. a->size = n;
  22. return 1; /* new userdatum is already on the stack */
  23. }
  24. static NumArray *checkarray (lua_State *L) {
  25. void *ud = luaL_checkudata(L, 1, "LuaBook.array"); //检验userdata类型
  26. luaL_argcheck(L, ud != NULL, 1, "`array' expected");
  27. return (NumArray *)ud;
  28. }
  29. static int getsize (lua_State *L) {
  30. NumArray *a = checkarray(L);
  31. lua_pushnumber(L, a->size);
  32. return 1;
  33. }
  34. static int setarray (lua_State *L) {
  35. double newvalue = luaL_checknumber(L, 3);
  36. *getelem(L) = newvalue;
  37. return 0;
  38. }
  39. static int getarray (lua_State *L) {
  40. lua_pushnumber(L, *getelem(L));
  41. return 1;
  42. }
  43. //获取指定索引的元素
  44. static double *getelem (lua_State *L) {
  45. NumArray *a = checkarray(L);
  46. int index = luaL_checkint(L, 2);
  47. luaL_argcheck(L, 1 <= index && index <= a->size, 2,"index out of range");
  48. /* return element address */
  49. return &a->values[index - 1];
  50. }

改进2:类的方式访问

要能够像下面这样,是对象的方式操作(:冒号)。

  1. a = array.new(1000)
  2. print(a:size()) --> 1000
  3. a:set(10, 3.4)
  4. print(a:get(10)) --> 3.4
  5. --------------思路--------------
  6. --在lua可以直接如下设置。在C中可以完全替代这些lua工作
  7. local metaarray = getmetatable(array.new(1))
  8. metaarray.__index = metaarray
  9. metaarray.set = array.set
  10. metaarray.get = array.get
  11. metaarray.size = array.size
  1. //getsize、getarray和setarray的实现前面已有
  2. static const struct luaL_reg arraylib_f [] = {
  3. {"new", newarray},
  4. {NULL, NULL}
  5. };
  6. static const struct luaL_reg arraylib_m [] = {
  7. {"__tostring", array2string},
  8. {"set", setarray},
  9. {"get", getarray},
  10. {"size", getsize},
  11. {NULL, NULL}
  12. };
  13. int luaopen_array (lua_State *L) {
  14. luaL_newmetatable(L, "LuaBook.array");
  15. lua_pushstring(L, "__index");
  16. lua_pushvalue(L, -2); /* pushes the metatable */
  17. lua_settable(L, -3); /* metatable.__index = metatable */
  18. //表名NULL,注册的函数没有在table中,在lua中直接使用函数
  19. luaL_openlib(L, NULL, arraylib_m, 0);
  20. luaL_openlib(L, "array", arraylib_f, 0);
  21. return 1;
  22. }
  23. int array2string (lua_State *L) {
  24. NumArray *a = checkarray(L);
  25. lua_pushfstring(L, "array(%d)", a->size);
  26. return 1;
  27. }

改进2:数组的方式访问

  1. a = array.new(1000)
  2. a[10] = 3.4 -- setarray
  3. print(a[10]) -- getarray --> 3.4
  4. --------------思路--------------
  5. --在lua可以直接如下设置。在C中可以完全替代这些lua工作
  6. local metaarray = getmetatable(newarray(1))
  7. metaarray.__index = array.get
  8. metaarray.__newindex = array.set
  1. //代替以上的lua思路
  2. int luaopen_array (lua_State *L) {
  3. luaL_newmetatable(L, "LuaBook.array");
  4. luaL_openlib(L, "array", arraylib, 0);
  5. /* now the stack has the metatable at index 1 and
  6. 'array' at index 2 */
  7. lua_pushstring(L, "__index");
  8. lua_pushstring(L, "get");
  9. lua_gettable(L, 2); /* get array.get */
  10. lua_settable(L, 1); /* metatable.__index = array.get */
  11. lua_pushstring(L, "__newindex");
  12. lua_pushstring(L, "set");
  13. lua_gettable(L, 2); /* get array.set */
  14. lua_settable(L, 1); /* metatable.__newindex = array.set */
  15. return 0;
  16. }

Light Userdata

light userdata是一个表示C指针的值(也就是一个void 类型的值),没有metatable。由于它是一个值,我们不能创建它们,像数字一样,不需要垃圾回收管理。
full userdata,表示一块内存区域。
light userdata,void
指针,可以指向任何类型,也就可以是light userdata指向full userdata。

  1. //将一个light userdatum直接入栈
  2. void lua_pushlightuserdata (lua_State *L, void *p);