四、爲LUA封裝C/C++函數(不涉及結構體等參數形式)
由上例中的int _cdecl MyCMax(lua_State* L)函數的實現,可以看出lua調用一個非lua_CFunction類型的函數的過程:
(1) 爲該函數實現一個lua_CFunction類型的函數(或模板)封裝。
(2) 調用LUA庫的註冊函數以某name註冊封裝函數
(3) 封裝函數從LUA棧中爲原函數(被封裝)獲取參數。
(4) 封裝函數使用(3)所獲取的參數call原函數。
(5) 最後封裝函數將原函數的返回值push到LUA棧。
要註冊一個非lua_CFunction類型的函數func到LUA中去,我們至少需要提供該函數的地址以及註冊名。那麼,想像中的封裝函數最簡略的形式看起來可能是這樣的:
這是我們希望的最簡略的註冊方式(一步到位,那是最好不過的,暫且假設它就是我們要實現的形式)。接下來是處理封裝函數,封裝函數必須具有lua_CFunction的形式:
template <typename _Ft> int lua_CFAdapter (lua_State* L);
但是,這就少了具體函數地址的信息。可以用類或結構體(它們能攜帶一些額外的信息,當然全局變量也可以)來彌補這個缺點(如下面代碼中的struct lua_CFAdapter)。下面直接給出基本代碼部分,以及最終註冊C函數的方式:
上面就是封裝的基本框架(指的是我正在做的,而不是專業封裝的基本框架),全部完成後可以下面的方式註冊C函數(這些函數的參數類型爲基本類型和各類指針):
void MyCPrint(const char* str);
lua_fbinder< void MyCPrint(const char* str, MyCPrint)>:: Register (L, "MyCPrint");
(非常遺憾的是要傳函數類型,還要直接傳遞函數名, 如果不考慮重載函數的情況,可以這樣處理。lua_fbinder<MyCPrint)>:: Register (L, "MyCPrint",MyCPrint); 這樣可做個宏:#define Lua_CFRegister(L, rname, fname) /
lua_fbinder< fname >:: Register (L, # rname, fname);
然後,用宏Lua_CFRegister(L, RegisterName, MyCPrint);即可。 止外接下來的實現還存在一個問題:不能註冊_stdcall的函數和可變參數的函數,當然在適當的地方加上_stdcall或修改即可,但這要拷貝一份部分代碼。)
對於C++函數,由於有重載的情況會出現歧義,因此,需要指明函數類型。
在struct lua_fbinder 中,模板參數_Fv是函數地址,每個函數都需要要封裝一次,而static int _cdecl lua_CFunction(lua_State* L)也很方便從中獲取函數地址。
從調用方式上看,還有比起全手寫還有那麼一點吸引力(有很多庫完成了很好的封裝,這裏僅是一種自我嘗試,娛樂),但是如何封裝呢?注意到struct lua_fbinder結構,其靜態函數int lua_CFunction ( lua_State *L )可由其獲得的信息只有C函數指針_Fv,但是函數參數類型和返回值類型都不十分明確。我們可以通過模板編程來自動匹配,從而獲取具體的信息。先看一下封裝函數的實現:
靜態函數int lua_CFunction ( lua_State *L )調用了一個函數_call_cfuncion(),這個函數,應該說是函數模板族,它有兩個參數,其中第二個就是函數類型,但在模板實現中,模板參數個數不再是單獨的_Ft,具體如下:
template <typename _R> inline int _call_cfuncion(lua_State* L, _R (*func)());
template <typename _R, typename _A> inline int _call_cfuncion(lua_State* L, _R (*func)(_A));
//當然不僅這兩個,上面只是無參數和一個參數的情況,由於處理相同,只給出兩個做示例。
函數特徵模板集成了函數參數和返回值的特徵,也就是映射了C數據類型與LUA數據類型的對應關係,即封裝了數據的push和pop操作。
下面是函數特徵模板function_traits的實現(其中數據特徵模板data_traits隨後給出):
數據特徵模板data_traits的實現(數據類型映射,並實現存取操作):
這是最後的一個模板_call_cfuncion2_t,也是實際進行棧操作和執行原函數的具體實現,下面是是上面提到的“錯誤做法”(別外的正確的做法可像上面的按參數個數實現):
這裏由於原函數的返回值類型爲void時,data_traits沒有實現任何操作(也就沒有入棧操作),所以需要分情況處理,就有了data_traits模板特化的情況。
最後,要指出的是,這裏沒有對不定參數的處理,但可以輕鬆(只是代碼不少,copy有點累)地來實現它。
參考資料:
(1)Lua 5.1 Reference Manual
(2)Programming in Lua
(3)http://www.cppblog.com/zzxhang/
(4)http://www.cppblog.com/kevinlynx/archive/2008/08/13/58684.html