Lua 基础之扩展应用程序

在 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"); 执行后栈顶是表 background
  • lua_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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值