通過上一篇的熱身,我們對C++調用lua變量有了一個認識,現在讓我們再深入一點,去探索一下如何調用lua的函數、表。
Lua與宿主通訊的關鍵——棧
lua是個動態腳本語言,它的數據類型如何映射到C++這種靜態類型語言中?lua是有GC機制的,這與C++手動管理內存相悖。如何解決這些問題呢?lua用一個抽象的棧與宿主語言交互,棧中的每一條記錄都可以保存lua值。無論何時,我們想要從lua請求一個值,調用lua,被請求的值將會被壓入棧。
棧是由lua來管理的,垃圾回收器知道哪個值正在被C使用(如果從lua中獲得一個字符串char*時,我們需要自己備份一個,不然被lua回收后,我們保存的指針將會失效)。lua以一個嚴格的后進先出規則來操作棧,當我們調用lua時,它只會改變棧頂部分。我們的C代碼有更多的自由,我們可以查詢棧上的任何元素,甚至在任何一個地方插入和刪除元素。
壓棧API有如下:
查詢棧中元素API:
其他的一些棧操作API:
調用lua函數
要調用一個函數,我們需要知道兩個信息:函數的地址和函數的簽名。如何獲得lua中的指定函數的地址?很簡單,與獲得變量的地址一樣,運用lua_getglobal(),第二個參數為lua函數的函數名。此時,該函數被壓入棧頂了,如果存在的話。既然在棧頂了,那么我們可以使用lua_pcall()來調用棧頂的函數。代碼如下:
運行之后,得到如下結果:
lua_pcall()這個API,我們之前已經接觸過了,那是在讀入文件、lua代碼后,即luaL_load*()之后執行的操作。在這里,其后面的三個參數依然為0。后面三個參數到底有什么用呢?我們先來看一下lua文檔:
lua_pcall()內部調用的是lua_call(),我們先來看看msgh這個參數,這個參數一般都設置為0,它只有在出錯的時候才會有可能用到。我們的精力花在前兩個參數上:nargs、nresults。
我來翻譯一下:你必須遵守下面的協議來調用函數:首先,被調用的函數要被壓入棧,該函數的參數要按照直接的順序壓入棧。第一個參數要第一個入棧,以此類推。最終你調用lua_call。nargs參數表示你壓入棧中的參數個數。該函數及其所有的參數在被調用的時候都會從棧中彈出。函數的返回值將會在函數返回的時候壓入棧中。返回值的個數用nresults這個參數來調整,除非傳入的是LUA_MULTRET(注:即-1)。在這種情況下,函數的全部返回值都會入棧。lua會處理棧的空間,以使所有的返回值都有充足的棧空間。所有的返回值是以直接入棧的順序壓棧的(即第一返回值先入,依次類推),所以經過調用后,最后一個返回值在棧頂。
原來,nargs是表示傳入參數個數,nresults指示返回參數個數。我們來寫代碼驗證一下
我們在test.lua中定義了一個函數,該函數帶有一個參數,兩個返回值。我們先不管返回值,先關注其參數。根據說明,我們要先壓入函數名,再壓入參數。C++代碼如下:
運行結果如下:
現在,我們來嘗試獲得返回值,根據lua函數的簽名,我們指定兩個返回值試試:
結果如下:
啊哈,是不是有種太簡單了的感覺?你的感覺沒錯,就這么簡單!根據文檔,返回值的個數我們可以隨意指定,如果感覺不過癮,可以試試將返回值指定為1,-1,3。試試看有什么結果。
取得lua表數據
與調用lua函數不同的地方在於,這里我們使用lua的另一個API:lua_gettable()。我們先來看下文檔:
翻譯如下:將t[k]的值壓入棧。t是棧中位於參數index所指向的位置,k為棧頂的值。此函數調用后,將會把棧頂的k的值出棧,將返回的結果放入棧頂。在lua中,這個調用將會觸發metamethod的“index”事件。
根據描述,我們有如下代碼:
結果如下:
至此,通過c++調用lua的各種數據的方式基本描述了一遍。接下來,我們要反客為主,用lua調用c++代碼,這就有點糾結了,特別是調用c++ dll的時候。不過,u r lucky,我已經解決了問題,解決的方法已經在第一篇的代碼中了。下一篇我我們一起來填坑。: )
總結
沒啥總結。C++調用lua簡單粗暴有用。值得一提的是,用sublime text寫腳本,真是太舒服了!用它寫lua、python爽快不止一點點。而且它還能用python擴展。強烈推薦大家寫腳本的時候使用它。