ref :https://blog.csdn.net/ouyangshima/article/details/43339571
LUA和C/C++的溝通橋梁——棧
Lua生來就是為了和C交互的,因此使用C擴展Lua或者將Lua嵌入到C當中都是非常流行的做法。要想理解C和Lua的交互方式,首先要回顧一下C語言是如何處理函數參數的。
C函數和參數
大家知道C語言是用匯編實現的,在匯編語言中可沒有函數的概念,與函數對應的是叫做子過程的東西,子過程就是一段指令,一個子過程與它調用的子過程之間通過棧來進行參數的傳遞交互。在一個子過程在調用別的子過程之前,會按照約定的格式將要調用的子過程需要的參數入棧,在被調用的子過程中,可以按照約定的規則將參數從棧中取出。同理,對於返回值的傳遞也同樣是通過堆棧進行的。C語言約定的參數放入棧中的格式,就是“調用慣例”。C語言的函數原型則決定了壓入棧中的參數的數量和類型。
Lua的虛擬堆棧
Lua和C之間的交互巧妙的模擬了C語言的堆棧,Lua和C語言之間的相互調用和訪問都通過堆棧來進行,巧妙的解決了不同類型之間變量相互訪問的問題。具體的,我們想象如下一個圖
由於C和Lua是不同層次的語言,因此C語言的變量和Lua中的變量以及函數不能直接的交互,我們假定C語言和Lua都有自己的“空間(C Space和Lua Space)”。而這兩個空間之間的交互就通過上圖中的這個虛擬堆棧來解決。為何采用虛擬堆棧的方式來進行交互呢?其目的是在提供強大的靈活性的同時避免交互時兩種語言變量類型的組合爆炸。
棧的使用解決了C和Lua之間兩個不協調的問題:第一,Lua會自動進行垃圾收集,而C要求顯示的分配存儲單元,兩者引起的矛盾。第二,Lua中的動態類型和C中的靜態類型不一致引起的混亂。
LuaAPI第一個程序
-
/*
-
下載安裝,LuaForWindows軟件(http://download.csdn.net/download/ivastest/3713327),安裝后,會有../lua5.1/inclue/;../lua5.1/lib/這兩文件,也就是我們編程要用的頭文件,庫文件;
-
或者,到官網(http://www.lua.org/download.html)下載源代碼,自己編譯出庫文件,百度搜索(vs2012編譯使用lua)教程
-
-
1.新建,Win32控制台應用程序——空項目,
-
2.配置,附加包含目錄,附加庫目錄,附加依賴項
-
*/
-
-
-
using namespace std;
-
-
-
/*#include<lua.hpp>其內容是:
-
extern "C" {
-
#include "lua.h"
-
#include "lualib.h"
-
#include "lauxlib.h"
-
}
-
*/
-
-
//#pragma comment(lib, "lua5.1.lib") // 這個是 Debug 版.
-
//#pragma comment(lib, "lua51.lib") // 這個是 Release 版.
-
-
/*
-
C語言讀寫Lua全局變量(基本類型)
-
C語言讀取Lua的全局變量是一種最簡單的操作。通過上圖我們可以猜測到,如果通過C語言讀取Lua中的全局變量需要兩步:1、將全局變量從Lua Space壓入虛擬堆棧;2、從堆棧將全局變量讀取到C語言Space中。在Lua和C的交互中,Lua無法看到和操作虛擬堆棧,僅在C語言中有操作堆棧的權利,因此前面說到的兩步全都是在C語言中完成的。
-
*/
-
void get_global(lua_State *L)
-
{
-
int global_var1;
-
lua_getglobal(L, "global_var1"); /* 從lua的變量空間中將全局變量global_var1讀取出來放入虛擬堆棧中 */
-
global_var1 = lua_tonumber(L, -1); /* 從虛擬堆棧中讀取剛才壓入堆棧的變量,-1表示讀取堆棧最頂端的元素 */
-
printf("Read global var from C: %d\n", global_var1);
-
}
-
-
int main()
-
{
-
lua_State *l = luaL_newstate(); // 創建 Lua 狀態. 其實就是一個數據結構.
-
luaL_openlibs(l); // 加載所有標准庫, math,table,os,debug,...
-
luaL_dofile(l, "test.lua");
-
//調試時,test.lua文件要與.cpp文件在同一目錄;且test.lua的編碼需要為ANSI格式,否則會執行失敗(0:成功),(1:失敗)
-
get_global(l);
-
lua_close(l);
-
system( "pause");
-
return 0;
-
}
-
/*
-
test.lua文件內容:
-
print("Hello world, from ",_VERSION,"!")
-
global_var1 = 5
-
print("Print global varb from lua", global_var1)
-
-
輸出:
-
Hello world, from Lua 5.1 !
-
Print global varb from lua 5
-
Read global var from C: 5
-
*/
-
-
/*
-
Lua腳本的編譯執行是相互獨立的,在不同的線程上執 行。通過luaL_newstate()函數可以申請一個虛擬機,返回指針類型lua_State。今后其他所有Lua Api函數的調用都需要此指針作為第一參數,用來指定某個虛擬機。所以lua_State代表一個lua虛擬機對像,luaL_newstate()分配 一個虛擬機。lua類庫管理着所有的虛擬機。銷毀指定虛擬機的所有對像(如果有垃圾回收相關的無方法則會調用該方法)並收回所有由該虛擬機動態分配產生的 內存,在有些平台下我們不需要調用此函數,因為當主程序退出時,資源會被自然的釋放掉,但是但一個長時間運行的程序,比如后台運行的web服務器,需要立 即回收虛擬機資源以避免內存過高占用。
-
*/
LuaAPI整理
頭文件lua.h定義了Lua提供的基礎函數。其中包括創建一個新的Lua環境的函數(如lua_open),調用Lua函數(如lua_pcall)的函數,讀取/寫入Lua環境的全局變量的函數,注冊可以被Lua代碼調用的新函數的函數,等等。所有在lua.h中被定義的都有一個lua_前綴。
頭文件lauxlib.h定義了輔助庫(auxiliary library )提供的函數。同樣,所有在其中定義的函數等都以luaL_打頭(例如,luaL_loadbuffer)。輔助庫利用lua.h中提供的基礎函數提供了更高層次上的抽象;所有Lua標准庫都使用了auxlib。基礎API致力於economy and orthogonality,相反auxlib致力於實現一般任務的實用性。當然,基於你的程序的需要而創建其它的抽象也是非常容易的。需要銘記在心的是,auxlib沒有存取Lua內部的權限。它完成它所有的工作都是通過正式的基本API。API中文介紹,Lua 5.1 參考手冊
堆棧
Lua以一個嚴格的LIFO規則(后進先出;也就是說,始終存取棧頂)來操作棧。當你調用Lua時,它只會改變棧頂部分。你的C代碼卻有更多的自由;更明確的來講,你可以查詢棧上的任何元素,甚至是在任何一個位置插入和刪除元素。棧中的元素通過索引值進行定位,其中棧頂是-1,棧底是1。 棧成員訪問支持索引。需要注意的是:堆棧操作是基於棧頂的,就是說它只會去操作棧頂的值。
C++數據傳遞到虛擬棧中
-
/* push functions (C -> stack)------>C空間與虛擬棧之間的操作
-
C語言向虛擬棧中壓人符合Lua數據類型(nil,number,string,table,function,userdata,thread)的變量
-
-
LUA_API void (lua_pushnil) (lua_State *L);
-
LUA_API void (lua_pushnumber) (lua_State *L, lua_Number n);--double,float
-
LUA_API void (lua_pushinteger) (lua_State *L, lua_Integer n);--int,long
-
LUA_API void (lua_pushlstring) (lua_State *L, const char *s, size_t l);--任意的字符串(char*類型,允許包含'\0'字符)
-
LUA_API void (lua_pushstring) (lua_State *L, const char *s);--以'\0'結束的字符串(const char*)
-
LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt,va_list argp);
-
LUA_API const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...);
-
LUA_API void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n);
-
LUA_API void (lua_pushboolean) (lua_State *L, int b);
-
LUA_API void (lua_pushlightuserdata) (lua_State *L, void *p);
-
LUA_API int (lua_pushthread) (lua_State *L);
-
*/
Lua從來不保持一個指向外部字符串(或任何其它對象,除了C函數——它總是靜態指針)的指針。對於它保持的所有字符串,Lua要么做一份內部的拷貝要么重新利用已經存在的字符串。因此,一旦這些函數返回之后你可以自由的修改或是釋放你的緩沖區。
虛擬棧數據傳遞到C++中
-
/*access functions (stack -> C)------>C空間與虛擬棧之間的操作
-
--API提供了一套lua_is*函數來檢查一個元素是否是一個指定的類型,*可以是任何Lua類型。lua_isnumber和lua_isstring函數不檢查這個值是否是指定的類型,而是看它是否能被轉換成指定的那種類型。
-
LUA_API int (lua_isnumber) (lua_State *L, int idx);
-
LUA_API int (lua_isstring) (lua_State *L, int idx);
-
LUA_API int (lua_iscfunction) (lua_State *L, int idx);
-
LUA_API int (lua_isuserdata) (lua_State *L, int idx);
-
LUA_API int (lua_type) (lua_State *L, int idx);
-
LUA_API const char *(lua_typename) (lua_State *L, int tp);
-
-
--API提供了虛擬棧上的兩個數據的關系
-
LUA_API int (lua_equal) (lua_State *L, int idx1, int idx2);--索引 index1 和 index2 中的值相同的話,返回 1 。否則返回 0 。如果任何一個索引無效也會返回 0。
-
LUA_API int (lua_rawequal) (lua_State *L, int idx1, int idx2);
-
LUA_API int (lua_lessthan) (lua_State *L, int idx1, int idx2);
-
-
--虛擬棧上的lua類型的數據轉換成符合C++語言數據類型的數據(int,double,char*,function,void,struct/class(userdata),指針)
-
LUA_API lua_Number (lua_tonumber) (lua_State *L, int idx);
-
LUA_API lua_Integer (lua_tointeger) (lua_State *L, int idx);
-
LUA_API int (lua_toboolean) (lua_State *L, int idx);
-
LUA_API const char *(lua_tolstring) (lua_State *L, int idx, size_t *len);
-
LUA_API size_t (lua_objlen) (lua_State *L, int idx);
-
LUA_API lua_CFunction (lua_tocfunction) (lua_State *L, int idx);
-
LUA_API void *(lua_touserdata) (lua_State *L, int idx);
-
LUA_API lua_State *(lua_tothread) (lua_State *L, int idx);
-
LUA_API const void *(lua_topointer) (lua_State *L, int idx);
-
*/
Lua_tostring函數返回一個指向字符串的內部拷貝的指針。你不能修改它(使你想起那里有一個const)。只要這個指針對應的值還在棧內,Lua會保證這個指針一直有效。當一個C函數返回后,Lua會清理他的棧,所以,有一個原則:永遠不要將指向Lua字符串的指針保存到訪問他們的外部函數中。
lua_tostring返回的字符串結尾總會有一個字符結束標志0,但是字符串中間也可能包含0,lua_strlen返回字符串的實際長度。特殊情況下,假定棧頂的值是一個字符串,下面的斷言(assert)總是有效的:
-
const char *s = lua_tostring(L, -1); //any Lua string
-
size_t l = lua_strlen(L, -1); // its length
-
assert(s[l] == '\0');
-
assert( strlen(s) <= l);
Lua數據傳遞到虛擬棧中
-
/*get functions (Lua -> stack)------>Lua空間與虛擬棧之間的操作
-
-
LUA_API void (lua_gettable) (lua_State *L, int idx);//把 t[k] 值壓入堆棧,這里的 t 是指有效索引 index 指向的值,而 k 則是棧頂放的值。這個函數會彈出堆棧上的 key (把結果放在棧上相同位置)。在 Lua 中,這個函數可能觸發對應 "index" 事件的元方法
-
lua_getglobal(L, "mytable") <== push mytable
-
lua_pushnumber(L, 1) <== push key 1
-
lua_gettable(L, -2) <== pop key 1, push mytable[1]
-
-
LUA_API void (lua_getfield) (lua_State *L, int idx, const char *k);//把 t[k] 值壓入堆棧,這里的 t 是指有效索引 index 指向的值。在 Lua 中,這個函數可能觸發對應 "index" 事件的元方法
-
lua_getglobal(L, "mytable") <== push mytable
-
lua_getfield(L, -1, "x") <== push mytable["x"],作用同下面兩行調用
-
--lua_pushstring(L, "x") <== push key "x"
-
--lua_gettable(L,-2) <== pop key "x", push mytable["x"]
-
-
LUA_API void (lua_rawget) (lua_State *L, int idx);//類似於 Lua_gettable,但是作一次直接訪問(不觸發元方法)。
-
LUA_API void (lua_rawgeti) (lua_State *L, int idx, int n);//把 t[n] 的值壓棧,這里的 t 是指給定索引 index 處的一個值。這是一個直接訪問;就是說,它不會觸發元方法。
-
lua_getglobal(L, "mytable") <== push mytable
-
lua_rawgeti(L, -1, 1) <== push mytable[1],作用同下面兩行調用
-
--lua_pushnumber(L, 1) <== push key 1
-
--lua_rawget(L,-2) <== pop key 1, push mytable[1]
-
-
LUA_API void (lua_createtable) (lua_State *L, int narr, int nrec);//創建一個新的空 table 壓入堆棧。這個新 table 將被預分配 narr 個元素的數組空間以及 nrec 個元素的非數組空間。當你明確知道表中需要多少個元素時,預分配就非常有用。如果你不知道,可以使用函數 Lua_newtable。
-
-
LUA_API void *(lua_newuserdata) (lua_State *L, size_t sz);//這個函數分配分配一塊指定大小的內存塊,把內存塊地址作為一個完整的 userdata 壓入堆棧,並返回這個地址。
-
userdata 代表 Lua 中的 C 值。完整的 userdata 代表一塊內存。它是一個對象(就像 table 那樣的對象):你必須創建它,它有着自己的元表,而且它在被回收時,可以被監測到。一個完整的 userdata 只和它自己相等(在等於的原生作用下)。
-
當 Lua 通過 gc 元方法回收一個完整的 userdata 時, Lua 調用這個元方法並把 userdata 標記為已終止。等到這個 userdata 再次被收集的時候,Lua 會釋放掉相關的內存。
-
LUA_API int (lua_getmetatable) (lua_State *L, int objindex);//把給定索引指向的值的元表壓入堆棧。如果索引無效,或是這個值沒有元表,函數將返回 0 並且不會向棧上壓任何東西。
-
LUA_API void (lua_getfenv) (lua_State *L, int idx);//把索引處值的環境表壓入堆棧。
-
*/
虛擬棧數據傳遞到Lua空間中
-
/*set functions (stack -> Lua)------>Lua空間與虛擬棧之間的操作
-
-
LUA_API void (lua_settable) (lua_State *L, int idx);作一個等價於 t[k] = v 的操作,這里 t 是一個給定有效索引 index 處的值, v 指棧頂的值,而 k 是棧頂之下的那個值。這個函數會把鍵和值都從堆棧中彈出。和在 Lua 中一樣,這個函數可能觸發 "newindex" 事件的元方法。eg:
-
lua_getglobal(L, "mytable") <== push mytable
-
lua_pushnumber(L, 1) <== push key 1
-
lua_pushstring(L, "abc") <== push value "abc"
-
lua_settable(L, -3) <== mytable[1] = "abc", pop key & value
-
-
LUA_API void (lua_setfield) (lua_State *L, int idx, const char *k);//做一個等價於 t[k] = v 的操作,這里 t 是給出的有效索引 index 處的值,而 v 是棧頂的那個值。這個函數將把這個值彈出堆棧。跟在 Lua 中一樣,這個函數可能觸發一個 "newindex" 事件的元方法。eg:
-
lua_getglobal(L, "mytable") <== push mytable
-
lua_pushstring(L, "abc") <== push value "abc"
-
lua_setfield(L, -2, "x") <== mytable["x"] = "abc", pop value "abc"
-
-
LUA_API void (lua_rawset) (lua_State *L, int idx);//類似於 Lua_settable,但是是作一個直接賦值(不觸發元方法)。
-
LUA_API void (lua_rawseti) (lua_State *L, int idx, int n);//等價於 t[n] = v,這里的 t 是指給定索引 index 處的一個值,而 v 是棧頂的值。函數將把這個值彈出棧。賦值操作是直接的;就是說,不會觸發元方法。
-
lua_getglobal(L, "mytable") <== push mytable
-
lua_pushstring(L, "abc") <== push value "abc"
-
lua_rawseti(L, -2, 1) <== mytable[1] = "abc", pop value "abc"
-
-
LUA_API int (lua_setmetatable) (lua_State *L, int objindex);//把一個 table 彈出堆棧,並將其設為給定索引處的值的 metatable 。
-
LUA_API int (lua_setfenv) (lua_State *L, int idx);//從堆棧上彈出一個 table 並把它設為指定索引處值的新環境。如果指定索引處的值即不是函數又不是線程或是 userdata , Lua_setfenv 會返回 0 ,否則返回 1 。
-
*/
虛擬棧基本操作
-
/* basic stack manipulation--基礎棧操作
-
LUA_API int (lua_gettop) (lua_State *L);//獲取棧的高度,它也是棧頂元素的索引。注意一個負數索引-x對應於正數索引gettop-x+1
-
LUA_API void (lua_settop) (lua_State *L, int idx);//設置棧的高度。如果開始的棧頂高於新的棧頂,頂部的值被丟棄。否則,為了得到指定的大小這個函數壓入相應個數的空值(nil)到棧上。特別的,lua_settop(L,0)清空堆棧。
-
LUA_API void (lua_pushvalue) (lua_State *L, int idx);//壓入堆棧上指定索引的一個摶貝到棧頂,【增加一個元素到棧頂】
-
LUA_API void (lua_remove) (lua_State *L, int idx);//移除指定索引位置的元素,並將其上面所有的元素下移來填補這個位置的空白,【刪除了一個元素】
-
LUA_API void (lua_insert) (lua_State *L, int idx);//移動棧頂元素到指定索引的位置,並將這個索引位置上面的元素全部上移至棧頂被移動留下的空隔,【沒有增加一個元素,移動了元素的位置】
-
LUA_API void (lua_replace) (lua_State *L, int idx);//從棧頂彈出元素值並將其設置到指定索引位置,沒有任何移動操作。【刪除了一個元素,替換掉指定的元素】
-
LUA_API int (lua_checkstack) (lua_State *L, int sz);//檢查棧上是否有能插入n個元素的空間;沒有返回0
-
LUA_API void (lua_xmove) (lua_State *from, lua_State *to, int n);//將一個堆棧上的從棧頂起的n個元素 移到另一個堆棧上
-
-
lua的堆棧保持着后進先出的原則。如果棧開始於 10 20 30 40 50*(自底向上;`*´ 標記了棧頂),那么:
-
lua_pushvalue(L, 3) --> 10 20 30 40 50 30*
-
lua_pushvalue(L, -1) --> 10 20 30 40 50 30 30*
-
lua_remove(L, -3) --> 10 20 30 40 30 30*
-
lua_remove(L, 6) --> 10 20 30 40 30*
-
lua_insert(L, 1) --> 30 10 20 30 40*
-
lua_insert(L, -1) --> 30 10 20 30 40* (no effect)
-
lua_replace(L, 2) --> 30 40 20 30*
-
lua_settop(L, -3) --> 30 40*
-
lua_settop(L, 6) --> 30 40 nil nil nil nil*
-
*/
宏定義
-
/* some useful macros
-
#define lua_pop(L,n) lua_settop(L, -(n)-1)
-
#define lua_newtable(L) lua_createtable(L, 0, 0)
-
#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n)))
-
#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0)
-
#define lua_strlen(L,i) lua_objlen(L, (i))
-
-
#define lua_isfunction(L,n) (lua_type(L, (n)) == LUA_TFUNCTION)
-
#define lua_istable(L,n) (lua_type(L, (n)) == LUA_TTABLE)
-
#define lua_islightuserdata(L,n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA)
-
#define lua_isnil(L,n) (lua_type(L, (n)) == LUA_TNIL)
-
#define lua_isboolean(L,n) (lua_type(L, (n)) == LUA_TBOOLEAN)
-
#define lua_isthread(L,n) (lua_type(L, (n)) == LUA_TTHREAD)
-
#define lua_isnone(L,n) (lua_type(L, (n)) == LUA_TNONE)
-
#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= 0)
-
-
#define lua_pushliteral(L, s) lua_pushlstring(L, "" s, (sizeof(s)/sizeof(char))-1)
-
#define lua_setglobal(L,s) lua_setfield(L, LUA_GLOBALSINDEX, (s))
-
#define lua_getglobal(L,s) lua_getfield(L, LUA_GLOBALSINDEX, (s))
-
#define lua_tostring(L,i) lua_tolstring(L, (i), NULL)
-
-
-
//compatibility macros and functions
-
#define lua_open() luaL_newstate()
-
#define lua_getregistry(L) lua_pushvalue(L, LUA_REGISTRYINDEX)
-
#define lua_getgccount(L) lua_gc(L, LUA_GCCOUNT, 0)
-
#define lua_Chunkreader lua_Reader
-
#define lua_Chunkwriter lua_Writer
-
*/