第一版是为 Lua 5.0 编写的。虽然在很大程度上仍然适用于较新版本,但存在一些差异。
第四版针对 Lua 5.3,可在 Amazon 和其他书店购买。
购买本书,您还可以帮助 支持 Lua 项目。
用 Lua 编程 | ||
第四部分。C API 第 25 章。扩展您的应用程序 |
让我们采用这种态度:现在,我们还希望为窗口配置背景颜色。我们假设最终颜色规范由三个数字组成,其中每个数字都是 RGB 中的颜色分量。通常,在 C 中,这些数字是某个范围内的整数,如 [0,255]。在 Lua 中,因为所有数字都是实数,我们可以使用更自然的范围 [0,1]。
这里的一个简单方法是要求用户在不同的全局变量中设置每个分量
-- configuration file for program `pp' width = 200 height = 300 background_red = 0.30 background_green = 0.10 background_blue = 0这种方法有两个缺点:它太冗长(实际程序可能需要几十种不同的颜色,用于窗口背景、窗口前景、菜单背景等);并且没有办法预定义常用颜色,因此,稍后,用户可以简单地编写类似
background = WHITE
的内容。为了避免这些缺点,我们将使用一个表格来表示颜色
background = {r=0.30, g=0.10, b=0}使用表格为脚本提供了更多结构;现在,用户(或应用程序)可以轻松地为配置文件中的后续使用预定义颜色
BLUE = {r=0, g=0, b=1} ... background = BLUE要在 C 中获取这些值,我们可以执行以下操作
lua_getglobal(L, "background"); if (!lua_istable(L, -1)) error(L, "`background' is not a valid color table"); red = getfield("r"); green = getfield("g"); blue = getfield("b");和往常一样,我们首先获取全局变量
background
的值并确保它是一个表格。接下来,我们使用 getfield
获取每个颜色分量。此函数不是 API 的一部分;我们必须按如下方式定义它
#define MAX_COLOR 255 /* assume that table is on the stack top */ int getfield (const char *key) { int result; lua_pushstring(L, key); lua_gettable(L, -2); /* get 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); /* remove number */ return result; }同样,我们面临多态性问题:可能存在许多版本的
getfield
函数,它们的变化包括键类型、值类型、错误处理等。Lua API 提供了一个单一函数 lua_gettable
。它接收堆栈中表的位置,从堆栈中弹出键,并推送相应的值。我们的私有 getfield
假设表位于堆栈顶部;因此,在推送键(lua_pushstring
)后,表将位于索引 -2。在返回之前,getfield
从堆栈中弹出检索到的值,以使堆栈处于调用之前相同的级别。
我们将进一步扩展我们的示例,并为用户引入颜色名称。用户仍然可以使用颜色表,但也可以对更常见的颜色使用预定义的名称。要实现此功能,我们需要在 C 应用程序中使用一个颜色表
struct 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} /* sentinel */ };
我们的实现将创建具有颜色名称的全局变量,并使用颜色表初始化这些变量。结果与用户在其脚本中具有以下行相同
WHITE = {r=1, g=1, b=1} RED = {r=1, g=0, b=0} ...与这些用户定义的颜色唯一的区别是,应用程序在运行用户脚本之前在 C 中定义了这些颜色。
为了设置表字段,我们定义了一个辅助函数 setfield
;它将索引和字段值推送到堆栈上,然后调用 lua_settable
/* assume that table is at the top */ void setfield (const char *index, int value) { lua_pushstring(L, index); lua_pushnumber(L, (double)value/MAX_COLOR); lua_settable(L, -3); }与其他 API 函数类似,
lua_settable
适用于许多不同的类型,因此它从堆栈中获取所有操作数。它接收表索引作为参数,并弹出键和值。setfield
函数假定在调用之前表位于堆栈顶部(索引 -1);在推送索引和值后,表将位于索引 -3。
setcolor
函数定义了一种颜色。它必须创建一个表,设置适当的字段,并将此表分配给相应的全局变量
void setcolor (struct ColorTable *ct) { lua_newtable(L); /* creates a table */ setfield("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(L, ct->name); /* `name' = table */ }
lua_newtable
函数创建一个空表并将其推送到堆栈上;setfield
调用设置表字段;最后,lua_setglobal
弹出表并将其设置为具有给定名称的全局变量的值。
使用这些先前的函数,以下循环将注册应用程序全局环境中的所有颜色
int i = 0; while (colortable[i].name != NULL) setcolor(&colortable[i++]);请记住,应用程序必须在运行用户脚本之前执行此循环。
还有另一种实现命名颜色的选项。用户可以使用字符串表示颜色名称,而不是全局变量,将她的设置写为 background = "BLUE"
。因此,background
可以是表或字符串。使用此实现,应用程序在运行用户的脚本之前不需要执行任何操作。相反,它需要更多工作来获取颜色。当它获取变量 background
的值时,它必须测试该值是否为字符串类型,然后在颜色表中查找该字符串
lua_getglobal(L, "background"); if (lua_isstring(L, -1)) { const char *name = lua_tostring(L, -1); int i = 0; while (colortable[i].name != NULL && strcmp(colorname, colortable[i].name) != 0) i++; if (colortable[i].name == NULL) /* string not found? */ error(L, "invalid color name (%s)", colorname); else { /* use colortable[i] */ red = colortable[i].red; green = colortable[i].green; blue = colortable[i].blue; } } else if (lua_istable(L, -1)) { red = getfield("r"); green = getfield("g"); blue = getfield("b"); } else error(L, "invalid value for `background'");
最佳选项是什么?在 C 程序中,使用字符串表示选项不是一个好习惯,因为编译器无法检测拼写错误。然而,在 Lua 中,全局变量不需要声明,因此当用户拼错颜色名称时,Lua 不会发出任何错误信号。如果用户写了 WITE
而不是 WHITE
,则 background
变量会收到 nil(未初始化变量 WITE
的值),这就是应用程序所知道的全部:background
是 nil。没有其他关于错误的信息。另一方面,使用字符串时,background
的值将是拼写错误的字符串;因此,应用程序可以将该信息添加到错误消息中。应用程序还可以不区分大小写地比较字符串,以便用户可以编写 "white"
、"WHITE"
甚至 "White"
。此外,如果用户脚本很小并且有很多颜色,那么仅为了让用户选择几种颜色而注册数百种颜色(并创建数百个表和全局变量)可能会很奇怪。使用字符串,您可以避免这种开销。
版权所有 © 2003–2004 Roberto Ierusalimschy。保留所有权利。 |