lua学习25:《Lua程序设计(第2版)》第25章

来源:互联网 发布:南宁云宝宝大数据 编辑:程序博客网 时间:2024/06/04 19:13

1、C语言从lua文件里读取配置,可以用lua程序充当C程序的配置文件,这样设计的程序在更改某个配置信息时可以不需要修改C代码重新编译程序。

--lua文件,C程序从lua文件里读取width和height的配置

--定义窗口大小

width = 200

height = 300

C程序:

void load (char *filename, int *width, int *height){lua_State *L = luaL_newstate();  //打开 Lua ,创建一个新环境lua_openlibs(L);  //打开标准库if (luaL_loadfile(L, filename)  //读取文件中内容并将内容压入栈  || lua_pcall(L, 0, 0, 0))  //运行栈中内容,运行完后所有的参数以及函数本身都会出栈  {                //将栈顶的错误信息转换成字符串并输出error(L, "cannot run configuration file: %s", lua_tostring(L, -1)); }lua_getglobal(L, "width");  //获取width变量并压入栈顶lua_getglobal(L, "height");  //获取height变量并压入栈顶//此时height在栈顶(-1),width在栈顶下一个(-2)if (!lua_isnumber(L, -2))error(L, "`width' should be a number\n");if (!lua_isnumber(L, -1))error(L, "`height' should be a number\n");*width = (int)lua_tonumber(L, -2);*height = (int)lua_tonumber(L, -1);lua_close(L);}

error源码:

#include <stdarg.h>#include <stdio.h>#include <stdlib.h>void error (lua_State *L, const char *fmt, ...){va_list argp;va_start(argp, fmt);vfprintf(stderr, argp);va_end(argp);lua_close(L);exit(EXIT_FAILURE);}

2、C中读取table

#define MAX_COLOR       255/* 假设table位于栈顶 */int getfield (const char *key){int result;lua_pushstring(L, key);  //将key压栈lua_gettable(L, -2);  //将对应key值出栈,将key对应的value压栈,key压栈后原来位于栈顶的table的索引变为-2if (!lua_isnumber(L, -1))  //判断栈顶的value是不是可以转换为数字error(L, "invalid component in background color");result = (int)lua_tonumber(L, -1) * MAX_COLOR;lua_pop(L, 1);//这里第二个参数1是弹出元素的个数,不是索引return result;}
其中

lua_pushstring(L, key);

lua_gettable(L, -2);

等价于lua_getfield(L, -1, key),由于没有向栈中压入字符串key,所以当调用lua_getfield时,table的索引为-1。

 lua_gettable()等价于lua_rawget()

3、C中赋值table

/* 假设table位于栈顶 */void setfield (const char *index, int value){lua_pushstring(L, index);   //将key压栈lua_pushnumber(L, (double)value/MAX_COLOR);   //将key对应的value压栈lua_settable(L, -3);  //弹出栈中的key和value,将table[key]=value压栈}
其中

lua_pushstring(L, index);   //将key压栈
lua_settable(L, -3);  //弹出栈中的key和value,将table[key]=value压栈

等价于lua_setfield(L, -2, key),由于没有向栈中压入字符串key,vlaue压入栈顶,所以当调用lua_getfield时,table的索引为-2。

 lua_settable()等价于lua_rawset()

最后调用lua_setglobal(L, table_name)生成一个名为“table_name”的table,

#define lua_setglobal(L, S)  (lua_pushstring(L, S), lua_insert(L, -2), lua_settable(L, LUA_GLOBALSINDEX))

#define lua_setglobal(L, S)  lua_setfield(L, LUA_GLOBALSINDEX), S)
注5.2版本已经没有LUA_GLOBALSINDEX,编译的时候需要加 #define LUA_GLOBALSINDEX    LUA_RIDX_GLOBALS

4、C中调用lua函数

/* 调用Lua中定义的函数'f' */double f (double x, double y){double z;/* 压入函数和参数 */lua_getglobal(L, "f");  /* 从lua中获取全局函数'f'并压栈 */lua_pushnumber(L, x);  /* 压入第一个参数 */lua_pushnumber(L, y);  /* 压入第一个参数 *//* 完成lua中函数调用 (2个参数, 1个返回值),调用完成后会将函数已经参数出栈,故栈中只留返回值*/if (lua_pcall(L, 2, 1, 0) != 0)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;}

调用lua_pcall时,第2个参数是指定lua函数的参数的个数,第3个参数是指定lua函数的返回结果的个数。第4个参数可以指定一个错误处理函数。Lua中赋值操作一样,lua_pcall会根据你的要求调整返回结果的个数,多余的丢弃,少的用nil补足。在将结果入栈之前,lua_pcall会将栈内的函数和参数移除。如果函数返回多个结果,第一个结果被第一个入栈,因此如果有n个返回结果,第一个返回结果在栈中的位置为-n,最后一个返回结果在栈中的位置为-1

        如果lua_pcall运行时出现错误,lua_pcall会返回一个非0的结果。另外,他将错误信息入栈(仍然会先将函数和参数从栈中移除)。在将错误信息入栈之前,如果指定了错误处理函数,lua_pcall会调用错误处理函数。使用lua_pcall的最后一个参数来指定错误处理函数,0代表没有错误处理函数,也就是说最终的错误信息就是原始的错误信息。否则,那个参数应该是一个错误函数被加载的时候在栈中的索引,注意,在这种情况下,错误处理函数必须要在被调用函数和其参数入栈之前入栈,也就是必须位于待调用函数及其参数的下面。

对于一般错误,lua_pcall返回错误代码LUA_ERRRUN。有两种特殊情况,会返回特殊的错误代码,因为他们从来不会调用错误处理函数。第一种情况是,内存分配错误,对于这种错误,lua_pcall总是返回LUA_ERRMEM。第二种情况是,当Lua正在运行错误处理函数时发生错误,这种情况下,再次调用错误处理函数没有意义,所以lua_pcall立即返回错误代码LUA_ERRERR

5、看一个稍微高级的例子,我们使用Cvararg来封装对Lua函数的调用。我们的封装后的函数(call_va)接受被调用的函数明作为第一个参数,第二参数是一个描述参数和结果类型的字符串,最后是一个保存返回结果的变量指针的列表。使用这个函数,我们可以将前面的例子改写为:

call_va("f""dd>d", x, y, &z);

字符串 "dd>d" 表示函数有两个double类型的参数,一个double类型的返回结果。我们使用字母 'd' 表示double'i' 表示integer's' 表示strings'>' 作为参数和结果的分隔符。如果函数没有返回结果,'>' 是可选的。

#include <stdarg.h>void call_va (const char *func, const char *sig, ...){va_list vl;int narg, nres;/* 参数和结果的数量 */va_start(vl, sig);  //此时vl指向可变形参的首地址lua_getglobal(L, func);/* 压入函数 *//* 压入参数 */narg = 0;while (*sig){/* push arguments */switch (*sig++){case 'd':/* double argument */lua_pushnumber(L, va_arg(vl, double));break;case 'i':/* int argument */lua_pushnumber(L, va_arg(vl, int));break;case 's':/* string argument */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");  //检查栈中空间}nres = strlen(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++){//将结果复制到vl中case 'd':/* double result */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);}

        尽管这段代码具有一般性,这个函数和前面我们的例子有相同的步骤:将函数入栈,参数入栈,调用函数,获取返回结果。大部分代码都很直观,但也有一点技巧。首先,不需要检查func是否是一个函数,lua_pcall可以捕捉这个错误。第二,可以接受任意多个参数,所以必须检查栈的空间。第三,因为函数可能返回字符串,call_va不能从栈中弹出结果,在调用者获取临时字符串的结果之后(拷贝到其他的变量中),由调用者负责弹出结果。

0 0
原创粉丝点击