Lua通過一個“虛擬棧”(Lua State)與C/C++程序進行數據交互。
當在Lua里面操作這個棧的時候,每次操作的都是棧的頂部。而Lua的C API則有更多的控制權,可非常靈活地操縱這個棧的任意位置。
c/c++調用lua實際上是:c/c++獲取全局表中的lua變量或函數,然后把數據放入棧中,lua再去棧中取數據,然后返回數據對應的值到棧頂,再由棧頂返回c++。
lua調c/c++也一樣:先將c/c++的函數注冊到lua解釋器中,然后lua再去調用它們。
棧(Lua State)
注1:絕對索引是從1開始由棧底到棧頂依次增長的
注2:相對索引是從-1開始由棧頂到棧底依次遞減的(在lua API函數內部會將相對索引轉換為絕對索引)
注3:上圖棧的容量為7,棧頂絕對索引為5,有效索引范圍為:[1, 5],可接受索引范圍為:[1, 7]
注4:Lua虛擬機指令里寄存器索引是從0開始的,而Lua API里的棧索引是從1開始的,因此當需要把寄存器索引當成棧索引使用時,要進行+1
棧是FILO(先進后出)的。棧中每個元素為一個TValue類型。64位系統下,sizeof(TValue)=16,sizeof(Value)=8
其中boolean(布爾)、integer(整型)、double(浮點)、light userdata、light c function是直接存在棧上的
TString、Udata、Closure、Table、lua state在棧上只是一個指針,都為GC類型,當沒有被引用時會被lua的GC系統自動回收,具體結構如下:
將不同類型的變量壓棧
static int Square(lua_State* L) { double d = lua_tonumber(L, 1); /* get argument */ lua_pushnumber(L, d*d); /* push result */ return 1; /* number of results */ } #include <string.h> static int CClosureStrLen(lua_State* L) { const char* upval = lua_tostring(L, lua_upvalueindex(1));// get first upvalue lua_pushnumber(L, (int)strlen(upval)); /* push result */ return 1; } typedef struct Rect { float w, h; } Rect; Rect g_rc; /************** 測試代碼 **************/ lua_settop(L, 0); //把棧上所有元素移除 lua_pushnil(L); // 把nil壓棧 lua_pushboolean(L, 1); // 把布爾值true壓棧 lua_pushinteger(L, 35);// 把整型數35壓棧 lua_pushnumber(L, 12.8);// 把浮點數12.8壓棧 lua_pushcfunction(L, Square);// 把c函數Square壓棧 lua_pushlightuserdata(L, &g_rc);// 把全局變量Rect g_rc的指針壓棧 lua_pushstring(L, "Hello!");// 把字符串Hello!壓棧 lua_newtable(L);// 創建一個空表並壓棧 lua_newthread(L);// 創建一個Thread並壓棧 lua_newuserdata(L, sizeof(Rect)); //創建一個內存塊為Rect的full userdata,並壓棧 lua_pushstring(L, "cclosure upvalue");// 創建一個字符串upvalue,內容為cclosure upvalue,並壓棧 lua_pushcclosure(L, CClosureStrLen, 1);// 創建有1個upvalue的c函數閉包(upvalue為棧頂元素),成功后將棧頂1個upvalue出棧,並將自己入棧
打印棧
#include "lobject.h" #include "lstate.h" const TValue luaO_nilobject_ = { NILCONSTANT }; /* value at a non-valid index */ #define NONVALIDVALUE cast(TValue *, luaO_nilobject) /* test for pseudo index */ #define ispseudo(i) ((i) <= LUA_REGISTRYINDEX) static TValue* index2addr(lua_State* L, int idx) { CallInfo* ci = L->ci; if (idx > 0) { TValue* o = ci->func + idx; api_check(L, idx <= ci->top - (ci->func + 1), "unacceptable index"); if (o >= L->top) return NONVALIDVALUE; else return o; } else if (!ispseudo(idx)) { /* negative index */ api_check(L, idx != 0 && -idx <= L->top - (ci->func + 1), "invalid index"); return L->top + idx; } else if (idx == LUA_REGISTRYINDEX) return &G(L)->l_registry; else { /* upvalues */ idx = LUA_REGISTRYINDEX - idx; api_check(L, idx <= MAXUPVAL + 1, "upvalue index too large"); if (ttislcf(ci->func)) /* light C function? */ return NONVALIDVALUE; /* it has no upvalues */ else { CClosure* func = clCvalue(ci->func); return (idx <= func->nupvalues) ? &func->upvalue[idx - 1] : NONVALIDVALUE; } } } void DumpLuaStack(lua_State* L) { int ad = -1; int i = lua_gettop(L); printf("\n---------------- Stack Dump ----------------\n"); while (i) { StkId o = index2addr(L, i); int t = lua_type(L, i); switch (t) { case LUA_TSTRING: printf("%d[%d]:'%s'\n", i, ad, lua_tostring(L, i)); break; case LUA_TBOOLEAN: printf("%d[%d]: %s\n", i, ad, lua_toboolean(L, i) ? "true" : "false"); break; case LUA_TNUMBER: printf("%d[%d]: %g\n", i, ad, lua_tonumber(L, i)); break; case LUA_TFUNCTION: if (ttislcf(o)) { printf("%d[%d]: c %p\n", i, ad, fvalue(o)); // lua_CFunction } else if (ttisCclosure(o)) { printf("%d[%d]: c closure %p\n", i, ad, clCvalue(o)->f); // CClosure } else if (ttisLclosure(o)) { Proto* pinfo = clLvalue(o)->p; printf("%d[%d]: lua closure %s[%d,%d]\n", i, ad, getstr(pinfo->source), pinfo->linedefined, pinfo->lastlinedefined); } break; case LUA_TTABLE: printf("%d[%d]: table:%p\n", i, ad, hvalue(o)); // 等價於printf("%d[%d]: table:%p\n", i, ad, lua_topointer(L, i)); break; case LUA_TLIGHTUSERDATA: printf("%d[%d]: light userdata:%p\n", i, ad, pvalue(o)); // 等價於printf("%d[%d]: light userdata:%p\n", i, ad, lua_topointer(L, i)); break; case LUA_TUSERDATA: printf("%d[%d]: full userdata:%p\n", i, ad, uvalue(o)); break; case LUA_TTHREAD: printf("%d[%d]: thread:%p\n", i, ad, thvalue(o)); // 等價於printf("%d[%d]: thread:%p\n", i, ad, lua_topointer(L, i)); break; default: printf("%d[%d]: %s\n", i, ad, lua_typename(L, t)); break; } i--; ad--; } printf("---------------------------------------------\n"); }
上面示例的不同類型變量壓棧的最終結果如下:
C++解釋執行lua文件
Test1.lua內容如下:
print("Hello, Lua!")
LuaTest.cpp代碼如下:
#include <stdio.h> extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } int main(int argc, char *argv[]) { lua_State* L = lua_open(); // 創建一個lua虛擬機 luaL_openlibs(L); // 打開狀態機L中的所有 Lua 標准庫 luaL_dofile(L, "Test1.lua"); // 載入Test.lua並解釋執行 lua_close(L); // 關閉lua虛擬機 return 0; }
注:luaL_dofile函數實際上是執行了luaL_loadfile來加載lua文件,加載成功之后會編譯該代碼塊為一個Lua閉包放置在棧頂,並返回0。由於返回值為假,會繼續調用lua_pcall來執行該Lua閉包,最后把該Lua閉包彈出棧。
#define luaL_dofile(L, fn) \ (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0))
C++訪問lua
C++調用lua函數
Test2.lua內容如下:
function add(x,y) return x + y end mytable={} function mytable.StaticFunc() print("mytable.StaticFunc called.") end function mytable:Func() print("mytable:Func self:", self) end
LuaTest.cpp代碼如下:
#include <stdio.h> extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } int main(int argc, char *argv[]) { lua_State* L = lua_open(); luaL_openlibs(L); luaL_dofile(L, "Test2.lua"); lua_getglobal(L, "add"); // 獲取全局函數add,並壓入棧頂 lua_pushinteger(L, 30); // 將整型的值30壓入棧頂 lua_pushinteger(L, 50); // 將整型的值50壓入棧頂 lua_call(L, 2, 1); // 對棧頂的30和50執行add函數調用,執行完后將30, 50,add彈出棧,將結果80壓棧 注:2為參數個數,1為返回值個數 int sum = (int)lua_tointeger(L, -1); // 將棧頂80賦值給sum變量 lua_pop(L, 1); // 從棧頂彈出1個元素 printf("The sum is: %d\n", sum); // 調用mytable表的靜態函數 lua_getglobal(L, "mytable"); // 將名為mytable的全局table變量的值壓棧 lua_pushstring(L, "StaticFunc"); // 將函數名為StaticFunc壓棧 lua_gettable(L, -2); // 從索引為-2處的表中,讀取key(在棧頂處)為StaticFunc的函數名 讀取成功后,將key出棧,並將讀取到的函數名入棧 lua_call(L, 0, 0); // 執行完后將StaticFunc彈出棧 注: 第一個0表示參數個數為0,第二個0表示無返回值 // 調用mytable表的成員函數 采用新方法獲取函數名 lua_getfield(L, -1, "Func");// 從索引為-1處的表中,讀取key為Func的函數名 成功后將讀取到的函數名入棧 lua_pushvalue(L, -2); // 將索引為-2處的表復制一份並壓入棧頂 lua_call(L, 1, 0); // 執行完后將Func彈出棧 注: 1表示參數個數,即self指針,為當前table,第二個0表示無返回值 lua_close(L); return 0; }
C++讀寫Lua中的全局變量
Test3.lua內容如下:
sayhi="Hello Lua!" mytable={sex = "male", age=18}
LuaTest.cpp代碼如下:
#include <stdio.h> extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } int main(int argc, char *argv[]) { lua_State* L = lua_open(); luaL_openlibs(L); luaL_dofile(L, "Test3.lua"); lua_settop(L, 0); //把棧上所有元素移除 // ******讀取名為sayhi的字符串全局變量的值****** lua_getglobal(L, "sayhi");// 將名為sayhi的全局string變量的值壓棧 if (lua_isstring(L, -1) != 0) { const char* str = lua_tostring(L, -1); // 從棧頂讀取字符串內容 printf("str: %s\n", str); } // ******修改名為sayhi的字符串全局變量的值****** lua_pop(L, 1); // 從棧頂彈出1個元素 lua_pushstring(L, "Welcome Lua!"); // 將Welcome Lua!字符串壓棧 lua_setglobal(L, "sayhi");// 將棧頂的元素設置給全局變量sayhi lua_getglobal(L, "sayhi"); const char* str2 = lua_tostring(L, -1); // str2為Welcome Lua! // ******讀取名為mytable的table全局變量中的內容****** lua_getglobal(L, "mytable"); // 將名為mytable的全局table變量的值壓棧 lua_pushstring(L, "sex"); // 將字符串sex壓棧 lua_gettable(L, -2); // 從索引為-2處的表中,讀取key(在棧頂處)為sex的元素 讀取成功后,將key出棧,並將讀取到的元素入棧 // 另外一種方式:讀取table中的某個key的值 lua_getfield(L, -2, "age");// 從索引為-2處的表中,讀取key為age的元素 成功后將讀取到的元素入棧 int age = (int)lua_tointeger(L, -1); // 從棧頂讀取age 為18 const char* sex = lua_tostring(L, -2); // 從索引為-2處讀取sex 為male // ******修改名為mytable的table全局變量中的內容****** lua_pop(L, 2); // 從棧頂彈出2個元素 lua_pushstring(L, "age"); // 將字符串age壓棧 lua_pushinteger(L, 20); // 將整數20壓棧 lua_settable(L, -3); // 設置索引為-3處的表的key(在棧頂下一個,即age)對應的value為20(在棧頂處) 設置成功后,將棧頂的2個元素都出棧 // 另外一種方式:設置table中的某個key的值 lua_pushstring(L, "female"); // 將字符串female壓棧 lua_setfield(L, -2, "sex"); // 設置索引為-2處的表的key為sex對應的value為female(在棧頂處) 設置成功后,將棧頂的female出棧 lua_getfield(L, -1, "sex"); // 從索引為-1處的表中,讀取key為sex的元素 成功后將讀取到的元素入棧 lua_getfield(L, -2, "age"); // 從索引為-2處的表中,讀取key為age的元素 成功后將讀取到的元素入棧 int age2 = (int)lua_tointeger(L, -1); // 從棧頂讀取age2 為20 const char* sex2 = lua_tostring(L, -2); // 從索引為-2處讀取sex 為female lua_close(L); return 0; }
Lua訪問C++(無dll)
Lua調用C++全局函數
Test4.lua內容如下:
local avg, sum = average(10,20,30,40,50) print("The average is ", avg) print("The sum is ", sum)
LuaTest.cpp代碼如下:
#include <stdio.h> extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } static int Average(lua_State *L) { int n = lua_gettop(L); // 獲取棧上元素的個數 double sum = 0; for (int i = 1; i <= n; ++i) { sum += lua_tonumber(L, i); // 依次去除索引為1到n的元素,並累加到sum } lua_pushnumber(L, sum / n); // 將sum/n計算得到平均值壓棧 lua_pushnumber(L, sum); // 將sum壓棧 return 2; // 表明有2個返回值 } int main(int argc, char *argv[]) { lua_State* L = lua_open(); luaL_openlibs(L); lua_register(L, "average", Average); // 將c++的Average函數注冊成lua中的average方法 luaL_dofile(L, "Test4.lua"); lua_close(L); return 0; }
注:lua_register先用lua_pushcfunction把在c++函數壓入棧中,然后調用lua_setglobal來設置棧頂的c++函數對應lua函數名,最后彈出棧頂的c++函數。
這樣就可以把lua函數和c++函數建立綁定關系,使得在后續的lua腳本中使用lua函數名來調用該c++函數。
#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n)))
Lua調用C++成員函數
下面示例也是MyArray的userdata的實現
Test5.lua內容如下:
function MyArrayTest(size) local a1 = myarray.new(size) myarray.set(a1, 1, 25.6) print(myarray.size(a1)) print(myarray.get(a1, 1)) end
LuaTest.cpp代碼如下:
#include <stdio.h> extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } class MyArray { public: int GetSize() { return size; } void SetSize(int insize) { size = insize; } double GetAt(int index) { return values[index]; } void SetAt(int index, double value) { values[index] = value; } private: int size; double values[1]; }; static int newarray(lua_State* L) { int n = (int)luaL_checkinteger(L, 1); // 檢查棧上索引為1處的元素是否為整型,並返回該元素 size_t nbytes = sizeof(MyArray) + (n - 1) * sizeof(double); MyArray* a = (MyArray*)lua_newuserdata(L, nbytes); // 創建一個userdata,並壓棧 a->SetSize(n);// 設置數組的大小 return 1;//表示有1個返回值 } static int setarray(lua_State* L) { MyArray* a = (MyArray*)lua_touserdata(L, 1);// 獲取棧上索引為1處的元素,並轉換為userdata指針 int index = (int)luaL_checkinteger(L, 2);// 檢查棧上索引為1處的元素是否為整型,並返回該元素 double value = luaL_checknumber(L, 3);// 檢查棧上索引為1處的元素是否為number類型,並返回該元素 luaL_argcheck(L, a != nullptr, 1, "'array' expected"); luaL_argcheck(L, 1 <= index && index <= a->GetSize(), 2, "index out of range"); a->SetAt(index-1, value); // 設置數組索引為index-1處的值為value return 0; //表示沒有返回值 } static int getarray(lua_State* L) { MyArray* a = (MyArray*)lua_touserdata(L, 1);// 獲取棧上索引為1處的元素,並轉換為userdata指針 int index = (int)luaL_checkinteger(L, 2);// 檢查棧上索引為1處的元素是否為整型,並返回該元素 luaL_argcheck(L, a != nullptr, 1, "'array' expected"); luaL_argcheck(L, 1 <= index && index <= a->GetSize(), 2, "index out of range"); double value = a->GetAt(index-1);// 獲取數組索引為index-1處的值 lua_pushnumber(L, value); // 將獲取的值壓棧 return 1;//表示有1個返回值 } static int getsize(lua_State* L) { MyArray* a = (MyArray*)lua_touserdata(L, 1); // 獲取棧上索引為1處的元素,並轉換為userdata指針 luaL_argcheck(L, a != nullptr, 1, "'array' expected"); lua_pushnumber(L, a->GetSize()); // 獲取數組的大小並壓棧 return 1;//表示有1個返回值 } static const struct luaL_Reg MyArrayLib[] = { {"new", newarray}, {"set", setarray}, {"get", getarray}, {"size", getsize}, {nullptr, nullptr} }; int luaopen_MyArray(lua_State* L) { luaL_newlib(L, MyArrayLib); return 1; } int main(int argc, char *argv[]) { lua_State* L = lua_open(); luaL_openlibs(L); luaL_requiref(L, "myarray", luaopen_MyArray, 1); // 將MyArray相關方法注冊到全局表中,lua中的名為myarray luaL_dofile(L, "Test5.lua"); lua_getglobal(L, "MyArrayTest"); // 將函數名MyArrayTest壓棧 lua_pushinteger(L, 1000); // 傳入MyArray的size為1000 壓棧 lua_call(L, 1, 0); // 執行完后將MyArrayTest彈出棧 注: 1表示參數個數,第二個0表示無返回值 lua_close(L); return 0; }
注:luaL_newlib中一共包含3個函數
luaL_checkversion: 檢查Lua版本是否一致
luaL_newlibtable: 創建一個table並壓入棧頂,這其實也是一個宏,實際上調用的是lua_createtable。
luaL_setfuncs: 將luaL_Reg函數列表設置給剛剛壓入棧的表。luaL_Reg函數列表是一個名字(key)和函數指針(value)組成的數組。
#define luaL_newlib(L,l) \ (luaL_checkversion(L), luaL_newlibtable(L,l), luaL_setfuncs(L,l,0))
Lua訪問C++(獨立dll模塊)
Test6.lua內容如下:
local myarray=require "myLualib" local avg, sum = average(10,20,30,40,50) print("The average is ", avg) print("The sum is ", sum) function MyArrayTest(size) local a1 = myarray.new(size) myarray.set(a1, 1, 25.6) print(myarray.size(a1)) print(myarray.get(a1, 1)) end MyArrayTest(1000)
注1:local myarray=require "myLualib"等價於以下代碼
local myarray = nil local fnluaopen_myLualib = package.loadlib("myLualib.dll","luaopen_myLualib") -- 查找myLualib.dll中名為luaopen_myLualib函數 if fnluaopen_myLualib ~= nil then return myarray=fnluaopen_myLualib() -- 執行luaopen_myLualib函數 end
注2:為了保證能找到myLublib.dll,可將dll文件復制到Test5.lua文件的目錄下
myLualib.dll模塊
/*************************** myLualib.h ***************************/ #pragma once extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } #ifdef MYLUALIB_EXPORTS #define MYLUALIB_API __declspec(dllexport) #else #define MYLUALIB_API __declspec(dllimport) #endif extern "C" MYLUALIB_API int luaopen_myLualib(lua_State *L);//定義導出函數 /*************************** myLualib.cpp ***************************/ #include "myLualib.h" static int Average(lua_State *L) { int n = lua_gettop(L); // 獲取棧上元素的個數 double sum = 0; for (int i = 1; i <= n; ++i) { sum += lua_tonumber(L, i); // 依次去除索引為1到n的元素,並累加到sum } lua_pushnumber(L, sum / n); // 將sum/n計算得到平均值壓棧 lua_pushnumber(L, sum); // 將sum壓棧 return 2; // 表明有2個返回值 } class MyArray { public: int GetSize() { return size; } void SetSize(int insize) { size = insize; } double GetAt(int index) { return values[index]; } void SetAt(int index, double value) { values[index] = value; } private: int size; double values[1]; }; static int newarray(lua_State* L) { int n = (int)luaL_checkinteger(L, 1); // 檢查棧上索引為1處的元素是否為整型,並返回該元素 size_t nbytes = sizeof(MyArray) + (n - 1) * sizeof(double); MyArray* a = (MyArray*)lua_newuserdata(L, nbytes); // 創建一個userdata,並壓棧 a->SetSize(n);// 設置數組的大小 return 1;//表示有1個返回值 } static int setarray(lua_State* L) { MyArray* a = (MyArray*)lua_touserdata(L, 1);// 獲取棧上索引為1處的元素,並轉換為userdata指針 int index = (int)luaL_checkinteger(L, 2);// 檢查棧上索引為1處的元素是否為整型,並返回該元素 double value = luaL_checknumber(L, 3);// 檢查棧上索引為1處的元素是否為number類型,並返回該元素 luaL_argcheck(L, a != nullptr, 1, "'array' expected"); luaL_argcheck(L, 1 <= index && index <= a->GetSize(), 2, "index out of range"); a->SetAt(index-1, value); // 設置數組索引為index-1處的值為value return 0; //表示沒有返回值 } static int getarray(lua_State* L) { MyArray* a = (MyArray*)lua_touserdata(L, 1);// 獲取棧上索引為1處的元素,並轉換為userdata指針 int index = (int)luaL_checkinteger(L, 2);// 檢查棧上索引為1處的元素是否為整型,並返回該元素 luaL_argcheck(L, a != nullptr, 1, "'array' expected"); luaL_argcheck(L, 1 <= index && index <= a->GetSize(), 2, "index out of range"); double value = a->GetAt(index-1);// 獲取數組索引為index-1處的值 lua_pushnumber(L, value); // 將獲取的值壓棧 return 1;//表示有1個返回值 } static int getsize(lua_State* L) { MyArray* a = (MyArray*)lua_touserdata(L, 1); // 獲取棧上索引為1處的元素,並轉換為userdata指針 luaL_argcheck(L, a != nullptr, 1, "'array' expected"); lua_pushnumber(L, a->GetSize()); // 獲取數組的大小並壓棧 return 1;//表示有1個返回值 } static const struct luaL_Reg MyArrayLib[] = { {"new", newarray}, {"set", setarray}, {"get", getarray}, {"size", getsize}, {nullptr, nullptr} }; int luaopen_myLualib(lua_State* L) { lua_register(L, "average", Average); // 將c++的Average函數注冊成lua中的average方法 luaL_newlib(L, MyArrayLib); return 1; }
注:MyLuaLib模塊不要直接集成lua虛擬機的源代碼,應導入lua虛擬機的dll來使用,否則會報如下錯誤
multiple Lua VMs detected
exe宿主程序
#include <stdio.h> extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } int main(int argc, char *argv[]) { lua_State* L = lua_open(); luaL_openlibs(L); luaL_dofile(L, "Test6.lua"); lua_close(L); return 0; }
參考
Lua 5.3 Reference Manual(官方英文版)