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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章