lua代码格式化工具_FFLUA——C++嵌入Luaamp;扩展Lua利器
摘要:
在使用C++做服務器開發中,經常會使用到腳本技術,Lua是最優秀的嵌入式腳本之一。Lua的輕量、小巧、概念之簡單,都使他變得越來越受歡迎。本人也使用過python做嵌入式腳本,二者各有特點,關于python之后會寫相關的文章,python對于我而言更喜歡用來編寫工具,我前邊一些相關的算法也是用python來實現的。今天主要講Lua相關的開發技術。Lua具有如下特點:
- Lua 擁有虛擬機的概念,而其全部用標準C實現,不依賴任何庫即可編譯安裝,更令人欣喜的是,整個Lua 的實現代碼并不算多,可以直接繼承到項目中,并且對項目的編譯時間幾乎沒有什么影響
- Lua的虛擬機是線程安全的,這里講的線程安全級別指得是STL的線程安全級別,即一個lua虛擬機被一個線程訪問是安全的,多個lua虛擬機被多個線程分別訪問也是安全的,一個lua虛擬機被多個線程訪問是不安全的。
- Lua的概念非常少,數據結構只有table,這樣當使用Lua作為項目的配置文件時,即使沒有編程功底的策劃也可以很快上手編寫。
- Lua沒有原生的對象,沒有class的關鍵字,這也保障了其概念簡單,但是仍然是可以使用Lua面向對象編程的。
- Lua盡管小巧,卻支持比較先進的編程范式,lua 中的匿名函數和閉包會讓代碼寫起來更加 優雅和高效,如果某人使用的C++ 編譯器還比較老套,不支持C++11,那么可以盡快感受一下lua的匿名函數和閉包。
- Lua是最高效的嵌入式腳本之一(如果不能說最的話,目前證據顯示是最)。
- Lua的垃圾回收也可以讓C++程序收益匪淺,這也是C++結合腳本技術的重要優勢之一。
- Lua 的嵌入非常的容易,CAPI 相對比較簡潔,而且文檔清晰,當然Lua的Capi需要掌握Lua中獨特的堆棧的概念,但仍然足夠簡單。
- Lua的擴展也非常的容易,將C++是對象、函數導入到lua中會涉及到一些技巧,如果純粹使用lua CAPI會稍顯繁雜,幸運的是一些第三方庫簡化了這些操作,而FFLUA絕對是最好用的之一。
嵌入Lua:
嵌入lua腳本,必須要把lua腳本載入lua虛擬機,lua中的概念稱之為dofile,FFLUA中封裝了dofile的操作,由于lua文件可能集中放在某個目錄,FFLUA中也提供了設置lua腳本目錄的接口:
int add_package_path(const string& str_) int load_file(const string& file_name_) throw (lua_exception_t)load_file就是執行dofile操作,若出錯,則throw異常對象,可以使用exception引用目標對象使用what接口輸出代碼出錯的traceback。
當嵌入lua時,最簡單的情況是把lua腳本當成配置使用,那么需要獲取lua腳本中的變量和設置lua變量,FFLUA封裝了兩個接口用于此操作。lua是動態語言,變量可以被賦值為任何lua支持的類型,但C++是強類型的,所以兩個接口都是范型的:
template<typenameT>int get_global_variable(conststring& field_name_, T& ret_);template<typenameT>int get_global_variable(constchar* field_name_, T& ret_);有時需要直接執行一些lua語句,lua中有dostring的概念,FFLUA中封裝了單獨的接口run_string:
void run_string(constchar* str_)嵌入lua時最一般的情況是調用lua中的函數,lua的函數比C++更靈活,可以支持任意多個參數,若未賦值,自動設置為nil,并且可以返回多個返回值。無論如何,從C++角度講,當你嵌入lua調用lua函數時,你總希望lua的使用方式跟C++越像越好,你不希望繁復的處理調用函數的參數問題,比如C++數據轉換成lua能處理的數據,即無趣又容易出錯。正也正是FFLUA需要做到,封裝調用lua函數的操作,把賦值參數,調用函數,接收返回值的細節做到透明,C++調用者就像調用普通的C++函數一樣。使用FFLUA中調用lua函數使用call接口:
void call(constchar* func_name_)當調用出錯時,異常信息記錄了traceback。實際上,FFLUA重載了9個call函數,以來自動適配調用9個參數的lua函數。
template<typename RET>RET call(const char* func_name_) throw (lua_exception_t);......template<typename RET, typename ARG1, typename ARG2, typename ARG3, typename ARG4,typename ARG5, typename ARG6, typename ARG7, typename ARG8, typename ARG9>RET call(const char* func_name_, ARG1 arg1_, ARG2 arg2_, ARG3 arg3_,ARG4 arg4_, ARG5 arg5_, ARG6 arg6_, ARG7 arg7_,ARG8 arg8_, ARG9 arg9_) throw (lua_exception_t);需要注明的是:
- call接口的參數是范型的,自動會使用范型traits機制轉換成lua類型,并push到lua堆棧中
- call接口的返回值也是范式的,這就要求使用call時必須提供返回值的類型,如果lua函數不返回值會怎樣?lua中有個特性,只有nil和false的布爾值為false,所以當lua函數返回空時,你仍然可以使用bool類型接收參數,只是調用者忽略其返回值就行了。
- call只支持一個返回值,雖然lua可以返回多個值,但是call會忽略其他返回值,這也是為了盡可能像是調用C++函數,若要返回多個值,完全可以用table返回。
擴展LUA:
這也是非常重要的操作,嵌入lua總是和擴展lua相伴相行。lua若要操作C++中的對象或函數,那么必須先把C++對應的接口注冊都lua中。Lua CAPI提供了一系列的接口擁有完成此操作,但是關于堆棧的操作總是會稍顯頭疼,fflua極大的簡化了注冊C++對象和接口的操作,可以說是最簡單的注冊方式之一(如果不準說最的話)。首先我們整理一下需要哪些注冊操作:
- C++ 靜態函數注冊為lua中的全局函數,這樣在lua中調用C++函數就像是調用C++全局函數
- C++對象注冊成Lua中的對象,可以通過new接口在lua中創建C++對象
- C++類中的屬性注冊到lua,lua訪問對象的屬性就像是訪問table中的屬性一樣。
- C++類中的函數注冊到lua中,lua調用其接口就像是調用talbe中的接口一樣。
FFLUA中提供了一個范型接口,適配于注冊C++相關數據, FFLUA中提供了工具類用于生成仿函數中應該完成的注冊操作:
剛才提到的像lua中的所有注冊操作,都可以使用def操作完成。 示例如下:
//! 注冊子類,ctor(int) 為構造函數, foo_t為類型名稱, base_t為繼承的基類名稱fflua_register_t<foo_t, ctor(int)>(ls, "foo_t", "base_t").def(&foo_t::print, "print") //! 子類的函數.def(&foo_t::a, "a"); //! 子類的字段尤其特別的是,C++中的繼承可以在注冊到lua中被保持這樣注冊過基類的接口,子類就不需要重復注冊。
高級特性:
通過以上的介紹,也許你已經了解了FFLUA的設計原則,即:當在編寫C++代碼時,希望使用LUA就像使用C++本地的代碼一樣,而在lua中操作C++的數據和接口的時候,又希望C++用起來完全跟table一個樣。這樣可以大大減輕程序開發的工作,從而把精力更多放大設計和邏輯上。那么做到如何lua才算像C++,C++做到如何才算像lua呢?我們知道二者畢竟相差甚遠,我們只需要把常見的操作封裝成一直即可,不常見操作則特殊處理。常見操作有:
- C++ 調用lua函數,FFLUA已經封裝了call函數,保障了調用lua函數就像調用本地C++函數一樣方便
- C++注冊接口和對象到lua中,lua中操作對象就像操作table一樣直接。
- C++中除了自定義對象,STL是用的最多的了,C++希望lua中能夠接收STL的參數,或者能夠返回STL數據結構
- Lua中只有table數據結構,Lua希望C++的參數的數據結構支持table,并且lua可以直接把table作為返回值。
- C++的指針需要傳遞到lua中,同時也希望某些操作,lua可以把C++對象指針作為返回值
以上前兩者已經介紹了,而后三者FFLUA也是給予 完美支持。通過范型的C++封裝,可以將C++ STL完美的轉換成luatable,同時在lua返回table的時候,自動根據返回值類型將lua的table轉換成C++ STL。FFLUA中只要被注冊過的C++對象,都可以把其指針作為參數賦值給lua,甚至在lua中保存。當我講述以上特性的時候,都是在保證類型安全的前提下。重要的類型檢查有:
- STL轉成Luatable時,STL中的類型必須是lua支持的,包括基本類型和已經注冊過的C++對象指針。并且STL可以嵌套使用,如vector<list<int> >, 不要驚訝,這是支持的,不管嵌套多少層,都是支持的,使用C++模板的遞歸機制,該特性得到了完美支持。vector、list、set都會轉換成table的數組模式,key從1開始累加。而map類型自動適配為table字典。
- LUA中的table可以被當成返回值轉換成C++ STL,轉換跟上邊剛好是對應的,當然有一個限制,由于C++的STL類型必須是唯一的,如vector<int>的返回值就要求lua中的table所有值都是int。否則FFLUA會返回出錯,并提示類型轉換失敗
- 無論死調用lua中使用C++對象指針,還是LuA中返回C++對象指針,該對象必須是lua可以識別的,即已經被注冊的,否則FFLUA會提示轉換類型失敗。
關于重載:
關于重載LUA 可以使用lua中內部自己的reload,也可以將fflua對象銷毀后,重先創建一個,創建fflua對象的開銷和創建lua虛擬機的開銷一直,不會有附加開銷。
總結:
- FFLUA是簡化C++嵌入綁定lua腳本的類庫
- FFLUA只有三個頭文件,不依賴除lua之外的任何的類庫,開發者可以非常容易的使用FFLUA
- FFLUA 對于常用的STL數據結構進行了支持
- FFLUA 即使擁有了這么多特性,仍然保持了輕量,只要用過C++,只要用過lua,FFLUA的代碼就可以非常清晰的看清其實現,當你了解其內部實現時,你會發現FFLUA已經做到了極簡,范型模板展開后的代碼就跟你自己原生LUA CAPI 編寫的一樣直接。
- FFLUA的開源代碼:https://github.com/fanchy/fflua
完整的C++示例代碼:
#include <iostream> #include <string> #include <assert.h> using namespace std;#include "lua/fflua.h"using namespace ff;class base_t { public:base_t():v(789){}void dump(){printf("in %s a:%dn", __FUNCTION__, v);}int v; }; class foo_t: public base_t { public:foo_t(int b):a(b){printf("in %s b:%d this=%pn", __FUNCTION__, b, this);}~foo_t(){printf("in %sn", __FUNCTION__);}void print(int64_t a, base_t* p) const{printf("in foo_t::print a:%ld p:%pn", (long)a, p);}static void dumy(){printf("in %sn", __FUNCTION__);}int a; };//! lua talbe 可以自動轉換為stl 對象 void dumy(map<string, string> ret, vector<int> a, list<string> b, set<int64_t> c) {printf("in %s begin ------------n", __FUNCTION__);for (map<string, string>::iterator it = ret.begin(); it != ret.end(); ++it){printf("map:%s, val:%s:n", it->first.c_str(), it->second.c_str());}printf("in %s end ------------n", __FUNCTION__); }static void lua_reg(lua_State* ls) {//! 注冊基類函數, ctor() 為構造函數的類型fflua_register_t<base_t, ctor()>(ls, "base_t") //! 注冊構造函數.def(&base_t::dump, "dump") //! 注冊基類的函數.def(&base_t::v, "v"); //! 注冊基類的屬性//! 注冊子類,ctor(int) 為構造函數, foo_t為類型名稱, base_t為繼承的基類名稱fflua_register_t<foo_t, ctor(int)>(ls, "foo_t", "base_t").def(&foo_t::print, "print") //! 子類的函數.def(&foo_t::a, "a"); //! 子類的字段fflua_register_t<>(ls).def(&dumy, "dumy"); //! 注冊靜態函數 }int main(int argc, char* argv[]) {fflua_t fflua;try {//! 注冊C++ 對象到lua中fflua.reg(lua_reg);//! 載入lua文件fflua.add_package_path("./");fflua.load_file("test.lua");//! 獲取全局變量int var = 0;assert(0 == fflua.get_global_variable("test_var", var));//! 設置全局變量assert(0 == fflua.set_global_variable("test_var", ++var));//! 執行lua 語句fflua.run_string("print("exe run_string!!")");//! 調用lua函數, 基本類型作為參數int32_t arg1 = 1;float arg2 = 2;double arg3 = 3;string arg4 = "4";fflua.call<bool>("test_func", arg1, arg2, arg3, arg4);//! 調用lua函數,stl類型作為參數, 自動轉換為lua talbevector<int> vec; vec.push_back(100);list<float> lt; lt.push_back(99.99);set<string> st; st.insert("OhNIce");map<string, int> mp; mp["key"] = 200;fflua.call<string>("test_stl", vec, lt, st, mp);//! 調用lua 函數返回 talbe,自動轉換為stl結構vec = fflua.call<vector<int> >("test_return_stl_vector");lt = fflua.call<list<float> >("test_return_stl_list");st = fflua.call<set<string> >("test_return_stl_set");mp = fflua.call<map<string, int> >("test_return_stl_map");//! 調用lua函數,c++ 對象作為參數, foo_t 必須被注冊過foo_t* foo_ptr = new foo_t(456);fflua.call<bool>("test_object", foo_ptr);//! 調用lua函數,c++ 對象作為返回值, foo_t 必須被注冊過 assert(foo_ptr == fflua.call<foo_t*>("test_ret_object", foo_ptr));//! 調用lua函數,c++ 對象作為返回值, 自動轉換為基類base_t* base_ptr = fflua.call<base_t*>("test_ret_base_object", foo_ptr);assert(base_ptr == foo_ptr);}catch (exception& e){printf("exception:%sn", e.what());}return 0; }更多精彩文章 http://h2cloud.org
總結
以上是生活随笔為你收集整理的lua代码格式化工具_FFLUA——C++嵌入Luaamp;扩展Lua利器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mac连接群晖的服务器会自动断开_酷玩家
- 下一篇: 开发实习生做什么_实习生月薪6W,还有住