Programming in Lua, 2Nd Edition - Chapter 25: Extending Your Application

本文档介绍了如何使用Lua作为配置语言来扩展应用程序。通过示例展示了如何加载和运行Lua配置文件,获取配置文件中的窗口尺寸数据,以及如何处理表格以支持颜色配置。文章还讨论了使用表格来表示颜色,允许用户通过预定义的颜色名称或自定义值来配置背景色。此外,还介绍了如何在C代码中调用Lua函数,并提供了一个通用的调用函数`call_va`,以方便不同类型的参数和返回值的函数调用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

 

 

 

 

 

Chapter 25: Extending Your Application

 

Lua 的一种重要应用是作为一种配置语言。这一章我们将展示如何用Lua 来配置一个应用程序。

 

25.1 The Basics

 

加载lua配置文件初始化应用程序

 

-- config.lua

-- define window size

width = 200

height = 300

---------------

#include <stdio.h>

#include "lua.h"

#include "lauxlib.h"

#include "lualib.h"

/* 4 error function */

#include <stdarg.h>

#include <stdio.h>

#include <stdlib.h>

 

 

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 */

}

 

void error (lua_State *L, const char *fmt, ...) {

     va_list argp;

     va_start(argp, fmt);

     vfprintf(stderr, fmt, argp);

     va_end(argp);

     lua_close(L);

     exit(EXIT_FAILURE);

}

 

 

void load (lua_State *L, const char *fname, int *w, int *h) {

     if (luaL_loadfile(L, fname) || lua_pcall(L, 0, 0, 0))

         error(L, "cannot run config. file: %s", lua_tostring(L, -1));

     lua_getglobal(L, "width");

     lua_getglobal(L, "height");

     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");

     *w = lua_tointeger(L, -2);

     *h = lua_tointeger(L, -1);

}

 

#define FILE_NAME "./src/config.lua"

int width;

int height;

 

int main (void) {

     char buff[256];

     int error;

     lua_State *L = luaL_newstate(); /* opens Lua */

     luaL_openlibs(L); /* opens the standard libraries */

 

     load(L, FILE_NAME, &width, &height);

     printf("width:%d height:%d/n", width, height);

 

     printf("stack dump:/n");

     stackDump(L);

     while (fgets(buff, sizeof(buff), stdin) != NULL) {

         error = luaL_loadbuffer(L, buff, strlen(buff), "line") ||

              lua_pcall(L, 0, 0, 0);

         if (error) {

              fprintf(stderr, "%s", lua_tostring(L, -1));

              lua_pop(L, 1); /* pop error message from the stack */

         }

     }

     lua_close(L);

     return 0;

}

 

 

程序中使用luaL_loadfile 函数加载文件,然后用lua_pcall 运行已编译过的chunk如遇到错误(如语法错误),此函数将错误消息压入栈中,并返回非零错误码。我们的程序以-1 索引作为参数调用lua_tostring 从栈顶获得错误消息

 

调用lua_getglobal 两次,获取全局变量的值,每一次调用都会将相应的全局值压入栈中。所以,width 在栈中的索引是-2height 的索引是-1(位于栈顶)(因为这个栈之前是空的,你可以从栈底开始索引,1 是第一个值,2 是第二个值)。

 

这个任务使用Lua 来完成是否值得?对这种简单的任务,一个只有两数值的简单文件可能比使用Lua 更方便。既便如此,使用Lua 会获得一些好处。首先,Lua 为你处理所有语法细节;你的配置文件甚至可以有注释!其次,用户已经可以用它来做更复杂的配置。例如,脚本可以给用户一些提示信息,或它可以查询一个环境变量以选择一个合适的size

 

-- configuration file

if getenv("DISPLAY") == ":0.0" then

       width = 300; height = 300

else

       width = 200; height = 200

end

最后一个原因是,现在可以容易的为你的程序添加新配置。

 

25.2 Table Manipulation

 

现在,我们也想给窗体配置背景色。我们假设颜色是由三个数组成,既RGBC 中,这些数是整数,其范围类似[0, 255]Lua 中,因为所有数都是实数,我们可以使用更自然的范围[0, 1]

 

一种方法是让用户去设置每一个不同的全局变量:

 

-- configuration file

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}

 

现在可以方便的为用户(或为应用程序)在配置文件中预定义颜色:


 

-- config.lua

-- configuration file

width = 200

height = 300

BLUE = {r=0, g=0, b=1}

--<other color de[1]nitions>

background = BLUE

------------------------------

/* main.c */

/* 加载配置文件,运行整个chunk */

if (luaL_loadfile(L, fname) || lua_pcall(L, 0, 0, 0))

         print_error(L, "cannot run config. file: %s", lua_tostring(L, -1));

/* 将全局变量background 压栈 */

lua_getglobal(L, "background");

/* 确定栈顶是表 */

if (!lua_istable(L, -1))

print_error(L, "'background' is not a table");

red = getfield(L, "r");

green = getfield(L, "g");

blue = getfield(L, "b");

/* 函数定义 */

/* assume that table is on the stack top */

int getfield (lua_State *L, const char *key) {

     int result;

     lua_pushstring(L, key);

     lua_gettable(L, -2); /* get background[key] */

     if (!lua_isnumber(L, -1))

         print_error(L, "invalid component in background color");

     result = (int)lua_tonumber(L, -1) * MAX_COLOR;

     lua_pop(L, 1); /* remove number */

     return result;

}

 

 

以表在栈中的位置调用lua_gettable 函数,然后它从栈中取得key(我们事先将key 压入栈顶,lua_gettable key 弹出),接着又将相应用value 压栈


 

因为以一个stirng key 对表进行索引是很常见的操作,lua 5.1 为这种任务提供一个特别版的lua_gettablelua_getfield

 

lua_pushstring(L, key);

lua_gettable(L, -2); /* get background[key] */

 

使用这个函数,我们可以将上面这两行代码重写为:

 

lua_getfield(L, -1, key);

 

进一步扩展我们的例子,并引入颜色名。用户仍然可以使用颜色表,但对那些常见颜色她也可以使用预定义名字。为实现这个特性,我们需要在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},

     /* <other colors> */

     {NULL, 0, 0, 0} /* sentinel */

};

 

lua_setfield

 

/* assume that table is at the top */

void setfield (lua_State *L, const char *index, int value) {

     lua_pushstring(L, index);

     lua_pushnumber(L, (double)value/MAX_COLOR);

     lua_settable(L, -3);

}

 

lua_setfield 将上面的代码简化为:

 

void setfield (lua_State *L, const char *index, int value) {

     lua_pushnumber(L, (double)value/MAX_COLOR);

     lua_setfield(L, -2, index);

}

 

 

setcolor 函数定义单个颜色(预定义颜色是在配置文件创建,setcolor是在代码中创建颜色)。它创建新表,设置合适的域,并将这个表赋值给相应的全局变量:

 

void setcolor (lua_State *L, struct ColorTable *ct) {

     lua_newtable(L); /* creates a table */

     setfield(L, "r", ct->red); /* table.r = ct->r */

     setfield(L, "g", ct->green); /* table.g = ct->g */

     setfield(L, "b", ct->blue); /* table.b = ct->b */

     lua_setglobal(L, ct->name); /* 'name' = table */

}

 

lua_newtable 创建出的表在栈顶,setfield 给它添加了三个域,分别是rgb

 

最后lua_setglobal 弹出由secolor 创建的表,并把把这个表设置成全局变量。

 

C 颜色表创建lua 颜色表

 

int i = 0;

while (colortable[i].name != NULL)

     setcolor(L, &colortable[i++]);

 

让配置文件中的颜色既可以是stirng,也可以是表

 

lua_getglobal(L, "background");

if (lua_isstring(L, -1)) { /* value is a string? */

     const char *name = lua_tostring(L, -1); /* get string */

     int i; /* search the color table */

     for (i = 0; colortable[i].name != NULL; i++) {

         if (strcmp(colorname, colortable[i].name) == 0)

              break;

     }

     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(L, "r");

     green = getfield(L, "g");

     blue = getfield(L, "b");

} else

error(L, "invalid value for 'background'");

 

string 来标记选项不是良好实践,因为编译器不能检测拼写错误。

 

 

--config.lua

-- configuration file

width = 200

height = 300

 

BLUE    = {r=0, g=0, b=1}

WHITE = {r=1, g=1, b=1}

RED      = {r=1, g=0, b=0}

 

background = BLUE

-----------------------

main.c

 

#include <stdio.h>

#include "lua.h"

#include "lauxlib.h"

#include "lualib.h"

/* 4 error function */

#include <stdarg.h>

#include <stdio.h>

#include <stdlib.h>

 

 

 

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 */

}

 

void print_error (lua_State *L, const char *fmt, ...) {

     va_list argp;

     va_start(argp, fmt);

     vfprintf(stderr, fmt, argp);

     va_end(argp);

     lua_close(L);

     exit(EXIT_FAILURE);

}

 

 

void load (lua_State *L, const char *fname, int *w, int *h) {

     if (luaL_loadfile(L, fname) || lua_pcall(L, 0, 0, 0))

         print_error(L, "cannot run config. file: %s", lua_tostring(L, -1));

     lua_getglobal(L, "width");

     lua_getglobal(L, "height");

     if (!lua_isnumber(L, -2))

         print_error(L, "'width' should be a number/n");

     if (!lua_isnumber(L, -1))

         print_error(L, "'height' should be a number/n");

     *w = lua_tointeger(L, -2);

     *h = lua_tointeger(L, -1);

}

 

#define MAX_COLOR 255

 

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},

     /* <other colors> */

     {NULL, 0, 0, 0} /* sentinel */

};

 

/* assume that table is on the stack top */

int getfield (lua_State *L, const char *key) {

     int result;

     lua_pushstring(L, key);

     lua_gettable(L, -2); /* get background[key] */

     if (!lua_isnumber(L, -1))

         print_error(L, "invalid component in background color");

     result = (int)lua_tonumber(L, -1) * MAX_COLOR;

     lua_pop(L, 1); /* remove number */

     return result;

}

 

/* assume that table is at the top */

void setfield (lua_State *L, const char *index, int value) {

     lua_pushnumber(L, (double)value/MAX_COLOR);

     lua_setfield(L, -2, index);

}

 

void setcolor (lua_State *L, struct ColorTable *ct) {

     lua_newtable(L); /* creates a table */

     setfield(L, "r", ct->red); /* table.r = ct->r */

     setfield(L, "g", ct->green); /* table.g = ct->g */

     setfield(L, "b", ct->blue); /* table.b = ct->b */

     lua_setglobal(L, ct->name); /* 'name' = table */

}

 

 

 

#define FILE_NAME "./src/config.lua"

int width;

int height;

int red;

int green;

int blue;

int i = 0;

 

int main (void) {

     char buff[256];

     int error;

     lua_State *L = luaL_newstate(); /* opens Lua */

     luaL_openlibs(L); /* opens the standard libraries */

 

     /* setcolor 后那些颜色表就成lua 环境中的全局变量了*/

     while (colortable[i].name != NULL)

         setcolor(L, &colortable[i++]);

 

     /* load 以后配置文件中的表会加载到栈中,但还不是全局变量?*/

     load(L, FILE_NAME, &width, &height);

     printf("width:%d height:%d/n", width, height);

     lua_getglobal(L, "background");

     if (!lua_istable(L, -1))

         print_error(L, "'background' is not a table");

     red = getfield(L, "r");

     green = getfield(L, "g");

     blue = getfield(L, "b");

 

     printf("stack dump:/n");

     stackDump(L);

     while (fgets(buff, sizeof(buff), stdin) != NULL) {

         error = luaL_loadbuffer(L, buff, strlen(buff), "line") ||

              lua_pcall(L, 0, 0, 0);

         if (error) {

              fprintf(stderr, "%s", lua_tostring(L, -1));

              lua_pop(L, 1); /* pop error message from the stack */

         }

     }

     lua_close(L);

     return 0;

}

 

 


 

25.3 Calling Lua Functions

 

C 代码中调用lua 函数

 

#include <stdio.h>

#include "lua.h"

#include "lauxlib.h"

#include "lualib.h"

 

#define ASSERT(value) if (!(value)) { _asm{int 3};}

#define FILENAME "./src/config.lua"

 

lua_State *L = NULL;

 

/* call a function 'f' defined in Lua */

double f (double x, double y) {

     double z;

     /* push functions and arguments */

     lua_getglobal(L, "f"); /* function to be called */

     lua_pushnumber(L, x); /* push 1st argument */

     lua_pushnumber(L, y); /* push 2nd argument */

     /* do the call (2 arguments, 1 result) */

     if (lua_pcall(L, 2, 1, 0) != 0) ASSERT(0);

     /* retrieve result */

     if (!lua_isnumber(L, -1)) ASSERT(0);

     z = lua_tonumber(L, -1);

     lua_pop(L, 1); /* pop returned value */

     return z;

}

 

int main (void) {

     char buff[256];

     int error;

     L = luaL_newstate(); /* opens Lua */

     luaL_openlibs(L); /* opens the standard libraries */

 

     if (luaL_loadfile(L, FILENAME) || lua_pcall(L, 0, 0, 0)) ASSERT(0);

    

     printf("%lf/n", f(2.0, 3.0)) ;

        

     while (fgets(buff, sizeof(buff), stdin) != NULL) {

         error = luaL_loadbuffer(L, buff, strlen(buff), "line") ||

              lua_pcall(L, 0, 0, 0);

         if (error) {

              fprintf(stderr, "%s", lua_tostring(L, -1));

              lua_pop(L, 1); /* pop error message from the stack */

         }

     }

     lua_close(L);

     return 0;

}

 

 

你要少返回值,lua_pcall 就返回多少给你,不够就push nil,多就丢弃:

 

/* 2 表示有两个参数,1 个返回值 */

if (lua_pcall(L, 2, 1, 0) != 0) ASSERT(0);

 

从栈顶往下数,前两个是参数,第三个就是要调用的函数。

 

lua_pcall 在压入返回值之前会从栈中移除刚才调用的函数及其参数

 

如果有多个返回值,第一个返回值先压栈。例如有三个返回值,第一个在栈中的索引是-3,最后一个-1

 

如果lua_pcall 运行期间出错,它返回一个非0 值,将错误消息压栈,将函数及其参数从栈中移除。在压入错误消息之前,它调用错误处理函数,如果有的话。第三个参数为0 意思是没有错误处理函数;这样,最终的错误消息就是原始消息。第三个参数如果不是0,参数值就是错误处理函数在栈中的索引,而且,它比函数及其参数更早被压入

 

对普通错误,lua_pcall 返回错误码LUA_ERRRUN。对内存分配错误,返回LUA_ERRMEM。如果是错误处理器本身出错,这种情况下立既返回LUA_ERRRUN

 


 

25.4 A Generic Call Function

 

lua 函数调用做一个封装:

 

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

 

其中f 是欲调用的函数名,dd 表示两个类型为double 的参数,> 是分隔符,d 表示有一返回值类型是doublex, y 是传给f 的参数,z 的意议很明显。 如果函数没有返回值,‘> 可以省略。

 

大部分代码和之前类似,但是这里有些微妙的地方。首先,它不检查func 是否是一个函数。lua_pcall 会引发所有错误。第二,因为它压入任意数量的参数,所以它必须检查栈空间。第三,因为函数可能返回stringscall_va 不能从栈中弹出结果,在调用者获取临时字符串的结果之后(拷贝到其他的buffer,由调用都来决定弹出它们。

 

封装对lua 的函数调用

 

--config.lua

function f (x, y)

       return (x^2 * math.sin(y))/(1 - x)

end

-----------------

-- main.c

#include <stdio.h>

#include "lua.h"

#include "lauxlib.h"

#include "lualib.h"

 

 

#define ASSERT(value) if (!(value)) { _asm{int 3};}

#define FILENAME "./src/config.lua"

 

lua_State *L = NULL;

 

#include <stdarg.h>

void call_va (const char *func, const char *sig, ...) {

     va_list vl;

     int narg, nres; /* number of arguments and results */

     va_start(vl, sig);

     lua_getglobal(L, func); /* push function */

     for (narg = 0; *sig; narg++) { /* repeat for each argument */

          /* check stack space */

         luaL_checkstack(L, 1, "too many arguments");

         switch (*sig++) {

              case 'd': /* double argument */

                   lua_pushnumber(L, va_arg(vl, double));

                   break;

              case 'i': /* int argument */

                   lua_pushinteger(L, va_arg(vl, int));

                  break;

              case 's': /* string argument */

                   lua_pushstring(L, va_arg(vl, char *));

                   break;

              case '>': /* end of arguments */

                   goto endargs;

              default:

                   ASSERT(0); //error(L, "invalid option (%c)", *(sig - 1));

         }

     }

     endargs:

    

     nres = strlen(sig); /* number of expected results */

     /* do the call */

     if (lua_pcall(L, narg, nres, 0) != 0) /* do the call */

         ASSERT(0); // error(L, "error calling '%s': %s", func,

     nres = -nres; /* stack index of first result */

     while (*sig) { /* repeat for each result */

         switch (*sig++) {

              case 'd': /* double result */

                   if (!lua_isnumber(L, nres))

                       ASSERT(0); //error(L, "wrong result type");

                   *va_arg(vl, double *) = lua_tonumber(L, nres);

                   break;

              case 'i': /* int result */

                   if (!lua_isnumber(L, nres))

                       ASSERT(0); //error(L, "wrong result type");

                   *va_arg(vl, int *) = lua_tointeger(L, nres);

                   break;

              case 's': /* string result */

                   if (!lua_isstring(L, nres))

                       ASSERT(0); // error(L, "wrong result type");

                   *va_arg(vl, const char **) = lua_tostring(L, nres);

                   break;

              default:

                   ASSERT(0);//error(L, "invalid option (%c)", *(sig - 1));

         }

         nres++;

     }

     va_end(vl);

}

 

/* call a function 'f' defined in Lua */

double f (double x, double y) {

     double z;

     /* push functions and arguments */

     lua_getglobal(L, "f"); /* function to be called */

     lua_pushnumber(L, x); /* push 1st argument */

     lua_pushnumber(L, y); /* push 2nd argument */

     /* do the call (2 arguments, 1 result) */

     if (lua_pcall(L, 2, 1, 0) != 0) ASSERT(0);

     /* retrieve result */

     if (!lua_isnumber(L, -1)) ASSERT(0);

     z = lua_tonumber(L, -1);

     lua_pop(L, 1); /* pop returned value */

     return z;

}

 

int main (void) {

     char buff[256];

     int error;

     double x, y, z;

     L = luaL_newstate(); /* opens Lua */

     luaL_openlibs(L); /* opens the standard libraries */

 

     if (luaL_loadfile(L, FILENAME) || lua_pcall(L, 0, 0, 0)) ASSERT(0);

    

     //printf("%lf/n", f(2.0, 3.0)) ;

     x=2.0;

     y=3.0;

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

     printf("%lf/n", z) ;

    

     while (fgets(buff, sizeof(buff), stdin) != NULL) {

         error = luaL_loadbuffer(L, buff, strlen(buff), "line") ||

              lua_pcall(L, 0, 0, 0);

         if (error) {

              fprintf(stderr, "%s", lua_tostring(L, -1));

              lua_pop(L, 1); /* pop error message from the stack */

         }

     }

     lua_close(L);

     return 0;

}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值