《Programming in Lua 3》读书笔记(二十二)
日期:2014.8.6
PartⅣ The C API
26 Extending Your Application
使用Lua很重要的一点是用来做配置语言。配合主语言做一些功能的配置。
26.1 The Basics
有的时候程序需要配置一些功能信息,很多时候可能有许多别的方法比用lua做配置要更简单:如使用环境变量或者读取文件,读取文件涉及到文件的解析。如果使用Lua进行配置的话,相当于用lua文件替代了要读取的如csv、txt文件等。
使用Lua进行配置的时候,就需要使用Lua API去控制Lua来解析文件了,然后从Lua的全局变量中得到要配置的值。Load函数承担了加载文件的功能,函数调用luaL_loadfile从给定名字的文件中读取语句快,然后调用lua_pcall来运行语句快。假如遇到了错误(例如:配置文件中的语法错误),此时函数将错误信息push至栈中然后返回一个非零的错误代码;此时我们的程序使用lua_tostring 带参数index -1 从栈顶得到信息。
e.g.
lua_tostring(L,-1) --从栈顶得到信息
加载完文件之后,就需要从Lua的全局变量中得到变量值了。此时调用lua_getglobal得到需要的值,参数为全局变量的名字。每一次调用都会将找到的全局变量值push值栈中,然后相应的主程序就从栈中得到相应的值。然后使用lua_is* 来确定值的类型,然后调用lua_to* 得到相应类型的值。
使用Lua做配置的好处就是,Lua帮你做了所有的语法检测处理,(配置文件中甚至可以有注释?);用户可以用Lua来做更为复杂的配置处理,如配置文件可以提示用户一些信息,或者检测某个环境变量来选择合适的值:
e.g. --configuration file if getenv("DISPLAY") == ":0.0" then width = 300;height = 300 else width = 200;height = 200
另外一个原因便是:现在来看很容易使用Lua给程序添加新的配置信息,这个特性使得程序更为灵活。
26.2 Table Manipulation
假设一个这样的场景:需要配置窗口的背景颜色。此时需要有三个值:RGB值。通常在C中,每个值的范围是[0,255],而Lua中,因为所有的number都是实数,所以使用的是[0,1]这个范围。
通常为每一个值声明一个变量会带来很多麻烦,如:假如程序需要多种配置,如前景色、按钮颜色等等,那这样会需要很多的变量;而且这种方法不适合用预定义颜色,或者说默认值。此时,Lua中的table就适合做这样的配置工作了:
e.g. background = {r = 0.30,g = 0.10,b = 0}
使用table将会给脚本带来非常大的便利性;此时也可以方便的使用一些预定值了,如:
e.g. BLUE = { r = 0, g = 0, b = 1.0} background = BLUE
而C代码中要获得这些值,可以如下来实现:
e.g. lua_getglobal(L,"background") if (! lua_istable(L,-1)) error(L,"'background' is not a table"); red = getcolorfield(L,"r"); green = getcolorfield(L,"g"); blue = getcolorfield(L,"b"); #define MAX_COLOR 255 int getcolorfield(lua_State *L,const char* key) { int result; //key 推出,而把相应的value推进栈 lua_pushstring(L,key); lua_gettable(L,-2); //前面两段可以用 lua_getfield(L,-1,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; }
26.3 Calling Lua Function
配置文件可以定义函数给程序调用,这是Lua的一大强势所在。
API调用一个函数是比较简单的:首先,将要调用的函数push至栈中;接着将要调用的参数push至栈中;然后可以使用lua_pcall 函数执行实际的函数调用;最后从栈中得到调用的结果。
lua_pcall 的第二个参数表示的是传递参数的数量和返回结果的数量;第四个参数指的是错误处理函数,0表示没有错误处理函数,其余的值表示函数在栈中的位置index,因此这些函数首先应该push至栈中的;Lua将会根据实际的函数的运算结果来调整参数和返回的数量,如果需要将会push ni或者抛弃额外的值。在将结果push之前,lua_pcall 将会从栈中移除掉函数和函数的参数。当函数返回了多个结果,第一个结果将首先被push。
如果在lua_pcall运行过程中遇到了任何的错误,lua_pcall将会返回一个错误代码;不仅如此,还会将错误信息push至stack中(并且仍然会推出函数和函数的参数)。在push错误信息之前,如果有错误处理函数,lua_pcall还是会首先执行错误处理函数的。
而普通的错误情况下,lua_pcall 返回的是 LUA_ERRUN.有两类错误将会返回不同的错误代码:第一种情况是内存配置错误,将会返回LUA_ERRMEM;第二种情况是当lua正在运行错误处理函数本身的时候,因为此时肯定不会再调用错误函数了,所以此时将会立即返回错误代码LUA_ERROR.在lua5.2中还定义了第三种情况,当finalizer(解释器?) 抛出错误的时候,此时将会返回LUA_ERRGCMM(error in a GC metamethod)。这个代码表明错误不与调用函数本身有关。
26.4 A Generic Call Function
这里,将会建立一个wrapper(包装器?)用来调用lua函数,使用了C中的vararg(可变参数?) 特性。假如称我们的包装器为call_va,参数为:1、要被调用的函数的名字;2、一个表示参数和返回值结果的字符串;3、参数列表;4、存储返回值结果的指针列表。使用API来处理所有的操作:
e.g. call_va(L,"f","dd>d",x,y,&z);
字符串"dd>d"表示“两个类型为double的参数,一个类型为double的返回值”,这里用"d"表示"double","i"表示"integer","s"表示"string"。用">"做分隔符。而如果函数没有返回值,那么">"是可选的。