在 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