Lua 是一種輕量小巧的腳本語言,也是號稱性能最高的腳本語言,它用C語言編寫並以源代碼形式開放。html
某些程序經常須要修改內容,而修改的內容不只僅是數據,更要修改不少函數的行爲。ios
而修改函數行爲這種事,很難用簡單的更改數據的方式來實現,若在源代碼層面上改又得從新編譯生成,致使修改爲本高。c++
而腳本語言先經過更改數據,並加了一層對數據解釋成運行代碼的步驟,從而使程序能在運行時更改複雜的函數行爲而無需從新編譯。git
它爲程序大大地提供了靈活的擴展和定製功能,減小了修改的成本。github
而遊戲程序每每會選擇性能高的LUA做爲腳本,來應對某些常常修改的模塊。編程
(本文使用Lua-5.3.5版本)數組
此外不建議編譯配置Lua靜態連接庫,否則用到某些函數缺乏dll會致使運行時錯誤編輯器
Lua庫C源碼:https://www.lua.org/download.html函數
下載lua-5.3.x.tar.gz文件,解壓。性能
建立DLL項目,選擇Release模式。
將解壓後的src文件夾下全部.h和.c文件(lua.c,luac.c和其餘格式文件都不要)拖進項目,
預處理器定義(宏定義)加上LUA_BULD_AS_DLL
而後項目生成dll文件和lib文件,這兩個文件就是編譯好出來的動態連接庫。
最後,在本身的工程項目裏,
dll文件複製過來放在生成文件夾(第一次編譯項目會在項目根目錄生成的Debug/Release文件夾)裏,
lib文件複製過來放在項目裏某個目錄,
那堆.h文件.c文件(lua.c,luac.c和其餘格式文件都不要)也要複製過來放在項目裏某個目錄,
配置好項目的包含目錄(放.h.c文件的那裏)和庫目錄(放lib文件的那裏)
至此,項目配置Lua庫完成
而後能夠在工程項目裏以下代碼包含lua庫:
#pragma comment(lib, "lua.lib") extern "C" { #include <lua.h> #include <lualib.h> #include <lauxlib.h> };
完整的語法教程->Lua編程參考文檔:http://book.luaer.cn/
部分Lua的基本變量類型:
nil | 無效值 |
boolean | 只有兩個值:false和true |
number | 雙精度類型的實浮點數(Lua的數字類型只有雙精度浮點數,並沒有整形單精度之分) |
string | 字符串由一對雙引號或單引號來表示 |
function | 由C或Lua編寫的函數 |
table | Lua 中的表(table)實際上是一個"關聯數組",數組的索引能夠是數字或者是字符串。 |
Lua在定義一個變量時,無需聲明它的類型:
a = 12
b = 250.520
c = "hello world"
d = {name = "asd",id = 2333}
條件:
if xxx then
xxxx
else
xxxx
end
Lua的函數能夠返還多個返還值
函數格式:
function xxx(xxxx)
end
在用C/C++使用Lua庫前,有必要理解它們的交互機制。
C與Lua交互的基礎是虛擬棧:
(如圖所示)
此外,爲了方便找到棧底棧頂元素的位置,這個虛擬棧還提供兩種索引:
正數索引和負數索引,從而使-1老是表明棧頂元素的索引,1老是表明棧底元素的索引
交互基本原理:
當C要調用Lua數據時,Lua把值壓入棧中,C再從棧中取值;
當Lua調用C數據時,C要將數據壓入棧中,讓Lua從棧中取值。
交互值時大部分能夠按上面的互相傳輸,可是交互函數稍微更復雜:
當C要調用Lua函數時,Lua先將Lua函數壓入棧中,C再將數據(做爲參數)繼續壓入棧中,
而後用API調用棧上的lua函數+參數,調用完後,Lua函數和參數都會出棧,而函數計算後的返還值會壓入棧中。
當Lua要調用C函數時,須要經過API註冊符合lua規範的C函數,來讓Lua知道該C函數的定義。
先編寫一個測試用的Lua腳本文件,
(因爲博主新裝電腦,暫時直接用記事本編輯,可是沒語法檢查容易出錯,這裏推薦使用其它專業的lua編輯器,例如vsc,lua studio等)
打開lua腳本文件:
char lua_filename[] = "test.lua"; lua_State *L = load_lua(lua_filename); if (NULL == L) { return -1; }
讀取lua文件的通常變量:
lua_getglobal(L, "str"); printf("str:%s\n",lua_tostring(L, -1)); lua_getglobal(L, "number"); printf("number:%f\n", lua_tonumber(L, -1));
讀取lua文件的table裏的變量:
lua_getglobal(L, "table"); //記錄table的索引 int tableIndex = lua_gettop(L); //對-1位置的table取name變量壓入棧頂 lua_getfield(L, -1, "name"); printf("table:name:%s\n",lua_tostring(L, -1)); //對tableIndex位置的table取table2變量壓入棧頂 lua_getfield(L, tableIndex, "table2"); //對-1位置的table2取name2變量壓入棧頂 lua_getfield(L, -1, "name2"); printf("table:table2:name2:%s\n", lua_tostring(L, -1));
讀取lua文件的函數,並調用之:
lua_getglobal(L, "add");//讀取函數到棧頂 lua_pushnumber(L, 10); //壓入參數 10 lua_pushnumber(L, 20); //壓入參數 20 //調用函數,若失敗返還非0 //lua_pcall第二個參數是指參數的數量,第三個參數是指返還值的數量 if (lua_pcall(L, 2, 1, 0) != 0) { printf("lua_pcall failed: %s\n", lua_tostring(L, -1)); return -1; } //讀取目前棧頂的元素,也就是返還值 double result = lua_tonumber(L, -1); printf("add result:%f\n",result);
執行上述代碼,咱們便能看到以下結果
將Lua腳本里的變量壓入棧中
//根據name獲取某個全局變量,壓入棧頂 int lua_getglobal(lua_State *L, const char *name); //根據name獲取index索引的table元素裏的某個變量,壓入棧頂 int lua_getfield(lua_State *L, int index, const char *name);
將C變量壓入棧中
//將數字壓入棧頂 void lua_pushnumber(lua_State *L,double number); //將字符串壓入棧頂 const char *lua_pushstring(lua_State *L, const char *str);
將棧中某個位置的元素提取成C變量
//將index索引的元素以數字的形式提取 double lua_tonumber(lua_State *L, int index); //將index索引的元素以字符串的形式提取,返還 const char* lua_tostring(lua_State *L, int index);
利用棧調用lua函數
//調用lua函數,arguNum是參數的個數,returnNum是返還值的個數,errorHandleIndex是函數調用錯誤時會另外調用的錯誤處理函數的索引(0視爲無) //調用前要求:依次壓入 lua函數元素,第1個參數元素,第2個參數元素.... //調用後:調用的lua函數元素和全部參數元素 會在棧裏被清理掉,而且若干個返還值元素將壓入棧頂 int lua_pcall(lua_State *L, int arguNum, int returnNum, int errorHandleIndex);
首先編寫好要調用的C函數,
可是這個C函數並不會像咱們往常編寫的「正宗C函數」。
首先該函數格式應爲:
static int xxxxx(lua_State *L) { //balabala隨便作點事什麼 return 一個數字; }
xxxxx的返還值 表明 註冊後該函數返還值的個數
那如何接受參數呢?這得經過上面介紹過的「將棧中某個位置的元素提取成C變量」方法獲取參數。
那如何返還返還值呢?一樣經過"將C變量壓入棧中"方法將返還值壓入棧頂。
例如1個參數、無返還值的print_num函數
static int print_num(lua_State *L) { double a = lua_tonumber(L, -1); printf("This num is %f", a);
return 0; }
有3個參數、1個返還值的add_three函數
static int add_three(lua_State *L) { int a = lua_tonumber(L, -1);
int b = lua_tonumber(L, -2);
int c = lua_tonumber(L, -3);
int sum = a + b + c; lua_pushnumber(L, sum); return 1; }
而後咱們在代碼裏用API將上述C函數註冊到Lua環境裏:
第二個參數爲在Lua腳本里要註冊的函數名字,第三個參數爲要註冊的C函數指針
lua_register(L, "print1", print_num); lua_register(L, "add3", add_three);
接下來修改腳本文件內容:
咱們看看在C調用Lua腳本的callCFunc(),這個函數裏面能不能正確調用回2個註冊的C函數。
1 char lua_filename[] = "test.lua"; 2 lua_State *L = load_lua(lua_filename); 3 if (NULL == L) { 4 return -1; 5 } 6 // 註冊函數 7 lua_register(L, "print1", print_num); 8 lua_register(L, "add3", add_three); 9 10 //調用Lua腳本的callCFunc函數 11 lua_getglobal(L, "callCFunc"); 12 lua_pcall(L, 0, 0, 0);
結果如咱們所料:
經過上面C與Lua的交互,咱們發現它們的交互機制並不簡單,
並且尚不支持C++這種更復雜的更多特性與Lua交互(類/對象/等)。
實際工程中咱們每每不想將注意力放在交互的底層過程,而是想如何方便的直接使用交互。
因而可使用github現有的庫以已達到C++與Lua方便交互的做用。
kaguya c++ binding下載地址:https://github.com/satoren/kaguya
kaguya是一個很易用的庫,它github的使用說明也十分淺顯易懂,就連它的配置也是十分簡單的:
首先確保你的項目已經包含了lua5.1~lua5.3的環境,
而後只需在你的項目添加"kaguya/include"目錄到項目的"頭文件包含目錄"便可。
本文就只簡單示範它的幾個用法(由於github的說明足夠詳細了,可自行查閱):
testkaguya.lua文件:
str = "Im dont know what to write" number = 250.520 table = { name = "Ezio", id = 123456, table2 = {name2 = "Auditore",id2 = 23333} } function useCppClass(obj) obj:a() end
C++測試用代碼:
1 #include <iostream> 2 #include <string> 3 #include "kaguya/kaguya.hpp" 4 5 using namespace std; 6 7 class Base { 8 private: 9 int shit; 10 public: 11 virtual void a() {cout << "Base::a()";} 12 }; 13 14 class Derived : public Base { 15 public: 16 virtual void a() { cout << "Derived::b()"; } 17 }; 18 19 int main() 20 { 21 //----初始化-----// 22 kaguya::State state; 23 state.dofile("testkaguya.lua"); 24 25 //-----執行lua代碼-----// 26 state("number2 = 233"); 27 state("str2 = 'ok'"); 28 29 //-----值交互----// 30 std::string value0 = state["str"]; 31 cout << value0 << endl << endl; 32 33 std::string value1 = state["str2"]; 34 cout << value1 << endl << endl; 35 36 double value2 = state["number"]; 37 cout << value2 << endl << endl; 38 39 std::string value3 = state["table"]["name"]; 40 cout << value3 << endl << endl; 41 42 state["tbl"] = kaguya::NewTable(); 43 state["tbl"]["value"] = 1; 44 double value4 = state["tbl"]["value"]; 45 cout << value4 << endl << endl; 46 47 //-----函數交互-----// 48 int funcReturn1 = state["math"]["abs"](-32); 49 assert(funcReturn1 == 32); 50 51 auto funcReturn2 = state["math"]["abs"].call<int>(-32); 52 assert(funcReturn2 == 32); 53 //-----類部分-----// 54 state["Base"].setClass(kaguya::UserdataMetatable<Base>() 55 .addFunction("a", &Base::a) 56 ); 57 state["Derived"].setClass(kaguya::UserdataMetatable<Derived, Base>() 58 .addFunction("a", &Derived::a) 59 ); 60 61 Base obj1; 62 Derived obj2; 63 state["useCppClass"](obj1); 64 state["useCppClass"](obj2); 65 66 system("pause"); 67 return 0; 68 }
測試結果: