在 c 中使用 lua
扩展应用程序 是指以 c 语言为主导,在 c 代码中调用 lua 代码,一种常见的方式是可以把 lua 文件当作配置文件,然后在 c 程序中加载解析
配置文件 config.lua 定义了一个窗口的相关属性
-- 定义窗口的宽高
width = 100
height = 200
在 c 程序中读取配置文件的内容
#include <stdio.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
void error(lua_State* L, const char* fmt, ...);
int main(void)
{
lua_State* L = luaL_newstate();
luaL_openlibs(L);
if (luaL_loadfile(L, "config.lua"))
{
error(L, "can not load file:%s", lua_tostring(L, -1));
}
if (lua_pcall(L, 0, 0, 0))
{
error(L, "can not run file:%s", lua_tostring(L, -1));
}
//获取全局变量压入栈中
lua_getglobal(L, "width");
lua_getglobal(L, "height");
if (!lua_isnumber(L, 1))
{
error(L, "width should be a number");
}
if (!lua_isnumber(L, 2))
{
error(L, "height should be a number");
}
float w = lua_tonumber(L, 1);
float h = lua_tonumber(L, 2);
printf("width: %g\nheight: %g\n", w, h);
lua_close(L);
getchar();
return 0;
}
void error(lua_State* L, const char* fmt, ...)
{
va_list arg_list;
va_start(arg_list, fmt);
vfprintf(stderr, fmt, arg_list);
va_end(arg_list);
lua_close(L);
exit(0);
}
读取 table 的域
读取 lua 中的 table 值和读取其它类型的值一样简单,只需要以下两步
* 使用 lua_getglobal(L, key)
获取全局变量,保存在栈顶
* 从栈顶取得该元素或弹出该元素,必要的时候先判断一下元素的类型
但 table 并不是值类型,因此不能直接把 table 从栈中弹出,这样做也没有意义,因为 c 语言并不能操作 lua 中的 table。因此我们需要取得 table 中的域,lua 提供了 lua_gettable
方法来取得 table 中的域
* 第一步,使用 lua_pushstring(L, key)
将键名压入栈,这时栈顶元素就是键名,而原来 table 的索引就变成 -2(如果原来在栈顶的话)。
* 第二步,使用 lua_gettable(L, -2)
从表中获取键值并压入栈,操作完毕后 lua 会把键名从栈中删除,这样栈顶元素是键值,而 table 的索引仍保持是 -2,如果我们取得键值后手动将键值从栈中删除,那么 table 的索引就会恢复成 -1,这样 table 又回到栈顶了(一般也会这么做)
* 从 lua5.1 开始提供了一种更简单的操作方式,就是把上面两步合并成一步,使用 lua_getfield
来实现
lua_pushstring(L, "w")
lua_gettable(L, -2)
等价于
lua_getfield(L, -1, "w")
下面用实例看一下如何读取 table 的域,在 config.lua 中定义了一个窗口的背景颜色
-- 定义窗口的宽高
width = 100
height = 200
-- 定义窗口颜色
background = {r = 255, g = 128, b = 0}
在 c 代码中读取窗口颜色
lua_getglobal(L, "background");
if (!lua_istable(L, -1))
{
error(L, "color should be a table");
}
//lua_getfield(L, -1, "r");
//lua_getfield(L, -2, "g");
//lua_getfield(L, -3, "b");
lua_pushstring(L, "r");
lua_gettable(L, -2);
lua_pushstring(L, "g");
lua_gettable(L, -3);
lua_pushstring(L, "b");
lua_gettable(L, -4);
if (!lua_isnumber(L, -1) || !lua_isnumber(L, -2) || !lua_isnumber(L, -3))
{
error(L, "color field should be a number");
}
float r = lua_tonumber(L, -3);
float g = lua_tonumber(L, -2);
float b = lua_tonumber(L, -1);
printf("color: (%g,%g,%g)\n", r, g, b);
lua_getglobal(L, "background");
执行后栈顶是表 backgroundlua_getfield(L, -1, "r");
执行后栈顶元素依次是 background.r background- 现在表 background 的索引变成 -2 了,所以取下一个域时要使用
lua_getfield(L, -2, "g");
- 所有取值操作之后,现在的栈顶元素依次是 background.b background.g background.r background …所以栈顶的三个元素就是 b g r 这三个域值了
设置 lua 变量的值
前面讲解了如何读取 lua 全局变量的值,下面讲解如何修改 lua 全局变量的值,其实跟取值差不多,不同的是调用相应函数之前要先把要设的值压入栈中
lua_setglobal 对应 lua_getglobal
lua_settable 对应 lua_gettable
lua_setfield 对应 lua_setfield
void setGlobalValue(lua_State* L)
{
int w, h, r, g, b;
printf("please input the new width:");
scanf("%d", &w);
printf("please input the new height:");
scanf("%d", &h);
printf("please input the new color red:");
scanf("%d", &r);
printf("please input the new color green:");
scanf("%d", &g);
printf("please input the new color blue:");
scanf("%d", &b);
//清空栈
lua_settop(L, 0);
//修改窗口宽高
lua_pushnumber(L, w);
lua_setglobal(L, "width");
lua_pushnumber(L, h);
lua_setglobal(L, "height");
//修改窗口颜色
lua_getglobal(L, "background");
/*lua_pushstring(L, "r");
lua_pushnumber(L, r);
lua_settable(L, -3);
lua_pushstring(L, "g");
lua_pushnumber(L, g);
lua_settable(L, -3);
lua_pushstring(L, "b");
lua_pushnumber(L, b);
lua_settable(L, -3);*/
lua_pushnumber(L, r);
lua_setfield(L, -2, "r");
lua_pushnumber(L, g);
lua_setfield(L, -2, "g");
lua_pushnumber(L, b);
lua_setfield(L, -2, "b");
}
调用 lua 函数
void callFunction(lua_State* L)
{
//获取函数名
lua_getglobal(L, "op");
//压入函数参数
lua_pushinteger(L, 5);
lua_pushinteger(L, 2);
//调用函数
lua_pcall(L, 2, 4, 0);
//获取计算结果
printf("5 + 2 = %d\n", lua_tointeger(L, -4));
printf("5 - 2 = %d\n", lua_tointeger(L, -3));
printf("5 * 2 = %d\n", lua_tointeger(L, -2));
printf("5 / 2 = %g\n", lua_tonumber(L, -1));
}
- 第一步,读取函数名压入栈中
- 第二步,按顺序将函数的参数压入栈中,第一个参数最先压入,依此类推
- 第三步,使用
lua_pcall
来调用函数,第二参数表示函数有几个参数,第三个参数表示期望的结果数量,最后一个参数是一个错误处理函数 - 函数返回值会根据期望的结果数量来调整,多的结果舍弃,少的补 nil,返回值会压入栈中,第一个返回值最先压入,最后一个返回值最后压入,因此最后的返回值会在栈顶。返回值压入前会先删除栈中的函数和参数
- 第四个参数为 0 时表示没有错误处理函数,函数调用发生错误时
lua_pcall
返回一个非零值并在栈中压入一条错误信息。可以在压入函数名之前将错误处理函数压入到栈中,然后将索引赋给lua_pcall
的第四个参数,这样函数调用发生错误时会先调用错误处理函数 - 普通错误会返回 LUA_ERRRUM,但有两种特殊情况不会调用错误处理函数。第一种,内存分配错误,返回 LUA_ERRMEM;第二种,在错误处理函数内部发生错误,返回 LUA_ERRERR