標籤:lua 函數調用 c 指令碼 互動
Lua是一種嵌入式語言,可以很好的嵌入其他應用程式。lua為我們提供了一組靈活的C API,使C代碼能夠很好的與Lua進行互動。包括讀寫Lua全域變數,調用Lua函數,運行Lua代碼,註冊C函數反過來供Lua調用。簡單的說,C能調用Lua,反過來Lua也能調用C。真的是灰常強大靈活的指令碼!!現在,先來學習一下怎麼用C調用Lua。
其實最簡單的我們已經做過了,通過一個dofile,運行一個lua指令檔。
一.棧
Lua與C的互動是通過一個虛擬棧進行的,這個棧對於Lua來說是嚴格的LIFO(後進先出)的,當調用Lua時,Lua只會改變棧的頂部。不過C有更大的自由度,可以檢索棧中元素,甚至在任意位置插入和刪除元素。
當Lua啟動或者Lua調用C語言時,棧中至少有20個空間的空閑槽,一般調用來說這些空間足夠了。如果調用的參數特別特別多,需要先檢查槽夠不夠用,使用下面的函數:
<span style="white-space:pre"></span>int lua_checkstack(lua_State* L, int sz );
API使用索引來引用棧中的元素,記住最開始的索引為1,不是0!即第一個壓入棧中的元素索引為1,第二個壓入棧中的元素索引為2,直到棧頂。也可以使用負數的索引來訪問棧頂的元素,即-1表示棧頂元素,以此類推。
在C語言的lua庫中,提供了幾個關於棧中元素操作的函數,由於C語言實現裡沒有泛型,所以,對應每一種資料類型都提供了一個函數,這裡後面的資料類型暫時用*代替。
//檢查棧中index索引的資料類型是否是*的類型int lua_is*(lua_State * L, int index)//返回棧中index索引的資料的類型int lua_type(lua_State* L, int index)//返回棧中index索引的資料的值,轉化為*的類型* lua_to*(lua_State* L, int index)//向棧中插入*類型的元素void lua_push*(lua_State* L, type*)
而既然這個東東是個棧,所以當然也提供了一些列棧本身的操作:
//獲得棧中元素個數int lua_gettop(lua_gettop) (lua_State *L);//設定棧頂為一個指定位置void lua_settop(lua_settop) (lua_State *L, int idx);//將指定索引上的值再次壓入棧void lua_pushvalue(lua_State *L, int idx);//刪除指定索引的元素,之上的向下移補缺void lua_remove(lua_State* L, int idx);//在index處開闢一個位置,上面的上移,然後將棧頂元素放到這個位置void lua_insert(lua_State* L, int idx);//彈出棧頂元素,使用該元素替代index元素void lua_replace(lua_State* L, int idx);
看一個例子,介個例子裡面木有使用lua,直接使用的C語言操作這個棧,並查看其中內容:
// LuaTest.cpp : 定義控制台應用程式的進入點。//#include "stdafx.h"#include <stdio.h>#include <windows.h>//因為Lua是C的函數,而我們的程式是C++的,所以要使用extern "C"引入標頭檔extern "C"{#include "lua.h"#include "lualib.h"#include "lauxlib.h"#include "luaconf.h"}//注意還需要添加之前編譯好的Lualib.lib檔案,這裡通過項目->屬性->連接器->附加依賴項添加了//否則需要 #pragma comment(lib, "lualib.lib")來添加//列印stack中的資料void CheckStack(lua_State* L){int top = lua_gettop(L);//stack的大小//遍曆stack所有層for (int i = 1; i <= top; i++){int type = lua_type(L, i);switch (type){case LUA_TSTRING://字串printf("%s\n", lua_tostring(L, i));break;case LUA_TBOOLEAN://布爾值printf(lua_toboolean(L, i) ? "true\n" : "false\n");break;case LUA_TNUMBER://數字printf("%d\n", lua_tonumber(L, i));break;default://其他值printf("%s\n", lua_typename(L, i));break;}}printf("\n");}int _tmain(int argc, _TCHAR* argv[]){//開啟lualua_State* L = luaL_newstate();//載入lib檔案luaL_openlibs(L);//向棧中壓入內容lua_pushboolean(L, 1);lua_pushstring(L, "hehe");lua_pushnumber(L, 100);//列印棧中內容CheckStack(L);//將index為1的內容再次壓入棧中lua_pushvalue(L, 1);CheckStack(L);//刪除index為2的元素lua_remove(L, 2);CheckStack(L);//設定棧頂為16(這個空了的地方貌似被補成每個類型一種,其餘為空白了)lua_settop(L, 16);CheckStack(L);//結束lua_close(L); system("pause");return 0;}結果:
true
hehe
0
true
hehe
0
true
true
0
true
true
0
true
string
table
function
userdata
thread
proto
(null)
(null)
(null)
(null)
(null)
(null)
(null)
請按任意鍵繼續. . .
二.簡單的調用Lua全域變數(可以作為設定檔)既然lua可以被C/C++的程式載入,直接載入程式,並將其作為設定檔也是一個好的用處。一個簡單的例子:
lua檔案:
--設定檔,包含兩個全域變數arg1 = 1arg2 = 2
C++程式:
// LuaTest.cpp : 定義控制台應用程式的進入點。//#include "stdafx.h"#include <stdio.h>#include <windows.h>//因為Lua是C的函數,而我們的程式是C++的,所以要使用extern "C"引入標頭檔extern "C"{#include "lua.h"#include "lualib.h"#include "lauxlib.h"#include "luaconf.h"}//注意還需要添加之前編譯好的Lualib.lib檔案,這裡通過項目->屬性->連接器->附加依賴項添加了//否則需要 #pragma comment(lib, "lualib.lib")來添加//列印stack中的資料void CheckStack(lua_State* L){int top = lua_gettop(L);//stack的大小//遍曆stack所有層for (int i = 1; i <= top; i++){int type = lua_type(L, i);switch (type){case LUA_TSTRING://字串printf("%s\n", lua_tostring(L, i));break;case LUA_TBOOLEAN://布爾值printf(lua_toboolean(L, i) ? "true\n" : "false\n");break;case LUA_TNUMBER://數字printf("%d\n", lua_tonumber(L, i));break;default://其他值printf("%s\n", lua_typename(L, i));break;}}printf("\n");}//讀取lua指令檔的函數(此處作為一個設定檔)void LoadLua(lua_State* L , const char* filename, int * arg1, int * arg2){if (luaL_loadfile(L, filename) || lua_pcall(L, 0, 0, 0))printf("loadfile failed! %s\n", lua_tostring(L, -1));//如果失敗,棧頂為錯誤資訊//獲得全域變數,壓入棧中lua_getglobal(L, "arg1");lua_getglobal(L, "arg2");//判斷一下素不素想要的類型if (!lua_isnumber(L, -2))printf("arg1 is not number\n");if (!lua_isnumber(L, -1))printf("arg2 is not number\n");//提取棧中的參數值*arg1 = lua_tointeger(L, -2);*arg2 = lua_tointeger(L, -1);}int _tmain(int argc, _TCHAR* argv[]){//開啟lualua_State* L = luaL_newstate();//載入lib檔案luaL_openlibs(L);int i, j = 0;LoadLua(L, "test.lua", &i, &j);printf("i = %d, j = %d\n", i , j);//結束lua_close(L); system("pause");return 0;}
結果:i = 1, j = 2
請按任意鍵繼續. . .
簡單解釋一下:
luaL_loadfile是載入lua檔案,作為一個程式塊,但是並不執行。
lua_pcall運行編譯好的程式塊。
lua_getglobal通過名稱獲得全域變數的值,壓入棧中。
這樣就通過C程式載入lua檔案,達到了載入設定檔的目的,雖然看起來比較麻煩,不過封裝一下的話,還是很好用的。而且使用Lua作為設定檔,一方面是容易操作,不需要額外寫一些設定檔讀取的工具,另一方面是可以在設定檔中添加註釋等等,甚至還可以寫一些條件判斷等等。
三.讀取table中的資訊 table類似於結構體,可以更加好的儲存一些資訊,當我們要儲存的設定檔內容較多,且雜亂時,將其分組就是很好的選擇。Lua支援C讀取table中的資訊。下面看一個例子:
lua檔案:
--設定檔,包含兩個全域變數struct = {arg1 = 1, arg2 = 2}
C++程式:
// LuaTest.cpp : 定義控制台應用程式的進入點。//#include "stdafx.h"#include <stdio.h>#include <windows.h>//因為Lua是C的函數,而我們的程式是C++的,所以要使用extern "C"引入標頭檔extern "C"{#include "lua.h"#include "lualib.h"#include "lauxlib.h"#include "luaconf.h"}//注意還需要添加之前編譯好的Lualib.lib檔案,這裡通過項目->屬性->連接器->附加依賴項添加了//否則需要 #pragma comment(lib, "lualib.lib")來添加//列印stack中的大小void CheckStack(lua_State* L){int top = lua_gettop(L);//stack的大小printf("The size of the stack is %d\n", top);}//讀取一個table的資料,table更加結構化void LoadLua(lua_State* L , const char* filename, int * arg1, int * arg2){//和直接讀取一樣,先載入file,然後執行if (luaL_loadfile(L, filename) || lua_pcall(L, 0, 0, 0))printf("loadfile failed! %s\n", lua_tostring(L, -1));//如果失敗,棧頂為錯誤資訊CheckStack(L);//此時stack為空白//將struct結構體放入棧中lua_getglobal(L, "struct");if (! lua_istable(L, -1))printf("struct is not table!\n");CheckStack(L);//此時棧大小為1//從struct中提取arg1放在棧頂lua_getfield(L, -1, "arg1");if (! lua_isnumber(L, -1))printf("arg1 is not number!\n");CheckStack(L);//此時棧大小為2*arg1 = lua_tointeger(L, -1);//上一個元素用完了,就把棧頂元素彈出lua_pop(L, 1);CheckStack(L);//此時棧大小為1//提取arg2放在棧頂lua_getfield(L, -1, "arg2");if (! lua_isnumber(L, -1))printf("arg2 is not number!\n");CheckStack(L);*arg2 = lua_tointeger(L, -1);}int _tmain(int argc, _TCHAR* argv[]){//開啟lualua_State* L = luaL_newstate();//載入lib檔案luaL_openlibs(L);int i, j = 0;LoadLua(L, "test.lua", &i, &j);printf("i = %d, j = %d\n", i , j);//結束lua_close(L); system("pause");return 0;}結果:The size of the stack is 0
The size of the stack is 1
The size of the stack is 2
The size of the stack is 1
The size of the stack is 2
i = 1, j = 2
請按任意鍵繼續. . .
還是上次的那兩個資料,不過這次他們被放在了一個table裡面,即使有相同的N組,也可以用N個table來儲存,不用擔心雜亂的問題。
簡單分析一下C讀取table的過程:
還是通過loadfile載入進來,然後仍然是getglobal獲得全域變數,但是這次的全域變數是一個table,在進行下一步操作之前先檢查一下是否真是個table,然後,通過另一個函數getfiled()獲得table中特定欄位的內容,放在棧頂。讀取一個之後,將其彈出,然後再次獲得下一個欄位的內容,讀取,以此類推。
趕腳這個table類型的設定檔就跟XML差不多了。一直沒找到C++下的好的XML解析器,實在不行以後就用Lua把。
四.調用Lua函數
終於到了這一步了,之前的僅僅能叫做設定檔,這一步才真正稱得上是指令碼!!其實使用C調用Lua指令碼比較簡單,還是使用那個棧進行參數的傳遞。
看一個簡單的例子:
Lua檔案:
--add fucntionfunction add(a, b)return a + bend
C++程式:
// LuaTest.cpp : 定義控制台應用程式的進入點。//#include "stdafx.h"#include <stdio.h>#include <windows.h>//因為Lua是C的函數,而我們的程式是C++的,所以要使用extern "C"引入標頭檔extern "C"{#include "lua.h"#include "lualib.h"#include "lauxlib.h"#include "luaconf.h"}//注意還需要添加之前編譯好的Lualib.lib檔案,這裡通過項目->屬性->連接器->附加依賴項添加了//否則需要 #pragma comment(lib, "lualib.lib")來添加//列印stack中的資料void CheckStack(lua_State* L){int top = lua_gettop(L);//stack的大小//遍曆stack所有層for (int i = 1; i <= top; i++){int type = lua_type(L, i);switch (type){case LUA_TSTRING://字串printf("%s ", lua_tostring(L, i));break;case LUA_TBOOLEAN://布爾值printf(lua_toboolean(L, i) ? "true " : "false ");break;case LUA_TNUMBER://數字printf("%g ", lua_tonumber(L, i));break;default://其他值printf("%s ", lua_typename(L, i));break;}}printf("\n");printf("The size of the stack is %d\n", top);}//讀取一個table的資料,table更加結構化void LoadLua(lua_State* L , const char* filename, int * arg1, int * arg2){//和直接讀取一樣,先載入file,然後執行if (luaL_loadfile(L, filename) || lua_pcall(L, 0, 0, 0))printf("loadfile failed! %s\n", lua_tostring(L, -1));//如果失敗,棧頂為錯誤資訊CheckStack(L);//此時stack為空白//將struct結構體放入棧中lua_getglobal(L, "struct");if (! lua_istable(L, -1))printf("struct is not table!\n");CheckStack(L);//此時棧大小為1//從struct中提取arg1放在棧頂lua_getfield(L, -1, "arg1");if (! lua_isnumber(L, -1))printf("arg1 is not number!\n");CheckStack(L);//此時棧大小為2*arg1 = lua_tointeger(L, -1);//上一個元素用完了,就把棧頂元素彈出lua_pop(L, 1);CheckStack(L);//此時棧大小為1//提取arg2放在棧頂lua_getfield(L, -1, "arg2");if (! lua_isnumber(L, -1))printf("arg2 is not number!\n");CheckStack(L);*arg2 = lua_tointeger(L, -1);}//調用Lua的函數double add(lua_State* L , double x, double y){//壓入函數和參數//提取lua中的函數,放入棧中lua_getglobal(L, "add");//將兩個參數壓入棧中lua_pushnumber(L, x);lua_pushnumber(L, y);CheckStack(L);//進行調用if (lua_pcall(L, 2, 1, 0) != 0)printf("function called failed! %s\n", lua_tostring(L, -1));CheckStack(L);//從棧中提取結果if (!lua_isnumber(L, -1))printf("function must return a double number!\n");double result = lua_tonumber(L, -1);lua_pop(L, 1);CheckStack(L);return result;}//讀取lua檔案並執行函數void GetLuaFunctiorn(lua_State* L, const char* filename){if (luaL_loadfile(L, filename) || lua_pcall(L, 0, 0, 0))printf("load file failed! %s\n", lua_tostring(L, -1));double resulet = add(L, 1.5, 2.5);printf("result is %f\n", resulet);}int _tmain(int argc, _TCHAR* argv[]){//開啟lualua_State* L = luaL_newstate();//載入lib檔案luaL_openlibs(L);//使用函數GetLuaFunctiorn(L, "test.lua");//結束lua_close(L); system("pause");return 0;}
結果:boolean 1.5 2.5
The size of the stack is 3
4
The size of the stack is 1
The size of the stack is 0
result is 4.000000
請按任意鍵繼續. . .
我們要調用Lua函數時,首先需要知道Lua中函數的名稱以及參數個數。這些都需要在調用前放入棧中。函數需要通過getglobal函數根據名稱提取,將函數放入棧中。然後將參數依次壓入棧中。準備工作做好之後,就要通過lua_pcall函數進行Lua函數調用了。lua_pcall函數一共有4個參數,第一個參數為lua_State,第二個參數為傳給函數的參數的數量,第三個參數為期望返回的結果的數量,最後一個參數為一個錯誤處理的函數的索引。與之前的賦值等操作一樣,如果我們實際的值比lua_pcall給的值多,會捨去,如果少的話,使用nil補齊。如果函數有多個返回參數的話,第一個參數先壓入棧中,然後是第二個,以此類推。這時,比如返回了三個結果,那麼第一個結果的索引為-3,第二個為-2,最後一個為-1。如果lua函數運行出錯的話,lua_pcall會返回一個非零的值,並且在棧中壓入一條錯誤資訊。但是在壓入錯誤資訊前,如果有錯誤處理函數,會先執行錯誤處理函數。lua_pcall的最後一個參數可以指定錯誤處理函數的位置,如果為0表明木有錯誤處理函數。如果有的話,這個參數表示錯誤處理函數在棧中的索引,所以如果有錯誤處理函數的話,就需要先把其壓入棧中。錯誤處理函數也不是什麼錯誤都可以處理的,當出現記憶體配置錯誤時或者在錯誤處理函數出錯時,都不會調用錯誤處理函數。
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
Lua學習筆記--C調用Lua