Quick-cocos2d-x luabinding 教程php
--基於quick-cocos2d-x 3.3rc1版本html
目錄node
Quick-cocos2d-x luabinding 教程ios
1. lua綁定原理app
3.3.6 修改函數參數和返回值類型,去除 const 修飾符
前言
本文主要目的是幫助使用quick-cocos2d-x(簡稱quick)的開發者快速的掌握在在quick如何綁定C-C++到lua,quick或者cocos2d-x對於lua綁定實際上是基於tolua++進行封裝的,爲了讀者更好的瞭解綁定原理,文章的開始會簡單介紹lua綁定和tolua++(第三方軟件軟件包)綁定原理。文中關於quick的luabinding說明徹底基於quick開發團隊官網關於luabinding的說明,爲了方便理解作了一下簡單調整和說明,若是你已經瞭解而且只想知道quick中luabinding的使用能夠直接從第三章開始閱讀。
在直接講解lua綁定原理前,咱們先進行一下掃盲,照顧一下一些對lua不是很瞭解的讀者。而且下面的分析對lua腳本整合於遊戲引擎的模式進行分析。
lua是一種免費的、輕量的、獨立的、可擴展的嵌入式程序語言,具備變量無類型、動態定義類型、面向對象、編譯產生中間代碼和內存自動回收的特色。正由於其這新特色常被做爲一種腳本嵌入到其餘系統中。
Lua是由標準的ANSIC語言實現的一個靜態庫,所以有對應各類操做系統的版本,簡單的說只要支持編制C的操做系統都支持。Lua語法簡單可是功能強大,可移植性高,被普遍的做爲腳本語言嵌入於主程序中。
Lua是一種嵌入式語言,依靠虛擬機運行。Lua系統是在state的機制上運行,state中包含運行環境的藉口信息,並且每一個state只能容納一個腳本文件裝載至內存中,要同時執行多個腳本必須初始化多個state。因此lua與其餘系統共享數據是經過棧來實現的。將函數和參數等信息壓棧來相互引用。下圖展現了lua引用引擎的函數,咱們用圖文結合的方式進行說明。
從圖中能夠看出要在引擎中調用lua腳本,必須先起一個state,在lua中要調用引擎中的腳本,須要引擎中先註冊,引擎中註冊後在亂中就能夠調用了。引擎中註冊的函數,對於lua而言至關於一個系統函數。在引擎中引用lua函數式一樣的原理,依然經過state。見下圖
在引擎中調用lua函數,首先須要將函數名壓站,而後將參數壓棧,而後調用lua_call執行,最後是從棧中取返回值圖中沒有畫出。
看到這裏相信你們對於lua與主程是如何相互引用的原理有了必定認識,下面咱們直接經過代碼演示,加深一下印象。
例1:純C環境下,註冊C函數進LUA環境
Main.c
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
int foo(lua_State *L)
{
int n = lua_tonumber(L, 1);foo函數參數
lua_pushnumber(L, n + 1);foo 返回值
return 1; foo函數返回值個數
}
int main()
{
lua_State *L = lua_open(); 初始化一個state
luaL_openlibs(L); 加載lua基本庫
lua_register(L, "foo", foo);註冊C函數foo
luaL_dofile(L, "a.lua");裝載,檢查,並當即執行a.lua
lua_close(L);與lua_open對應,關閉state
return 0;
}
文件a.lua
print(foo(99))
文件a.lua中就一條語句,加入lua虛擬機編譯運行後,在屏幕就看到輸出100例2:C環境下,調用lua函數
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
int main()
{
lua_State *L = lua_open(); 初始化一個state
luaL_openlibs(L); 加載lua基本庫
luaL_dofile(L, "b.lua");
lua_getglobal(L, "add");將函數名壓棧
lua_pushnumber(L, 51);參數壓棧
lua_pushnumber(L, 49);
lua_call(L,2,1);調用最近一次壓棧的函數
int result = (int) lua_tonumber(L,1); 從棧獲取返回值
lua_pop(L,1);從棧中清除返回值
lua_close(L);
return 0;
}
b.lua
function add(nubmer1, number2)
print(number1)
print(number2)
return number1 + number2
end
例2輸出的結果是51 49 100
經過這兩個例子相信你們已經看到lua和主程序相互調用很是的簡單,這裏用的是c語言的一個示例,使用起來很簡單,可是咱們通常須要幫定給lua的函數有不少,好比真個cocos2d-x的庫函數,這麼多函數要綁定註冊lua,那麼須要咱們重複寫多少上面同樣的代碼,並且每一個庫綁定都須要從新寫寫,這些繁瑣的工做也是一個很是痛苦的事情,更疼苦的是lua的本質是C,提供的函數都是C函數,要把一個C++的類註冊進lua造成一個table是否是要作更多工做來轉換,可是這些仍然是重複性。既然是重複性的工做,那麼有沒有什麼工具或者庫來幫咱們完成這樣的工做呢。答案固然是確定的,這就是咱們接下來要講的tolua++,一個三方軟件包。
tolua++是一種第三方的軟件包,能夠爲Lua提供面向對象的特性,更直接的說tolua++ 是一個將 C/C++ 的函數和對象導出給 Lua 腳本使用的工具,即幫咱們完成咱們上面的重複性的工做。
由於Lua的本質是C,不是C++,Lua提供給C用的API也都是基於面向過程的C函數來用的,要把C++類註冊進Lua造成一個一個的table環境是不太容易一會兒辦到的事,由於這須要繞着彎地把C++類變成各類其餘類型註冊進Lua,至關於用面向過程的思惟來維護一個面向對象的環境。這其中的細節就不去深究了,總之正是由於如此,因此單純地手寫lua_register()
等代碼來註冊C++類是行不通的、代價高昂的,因此須要藉助toLua++這個工具。
使用這個工具的基本步驟:
將要導出的 C/C++ 函數和對象定義寫入 .pkg 文件;
運行 tolua++ 工具,將 .pkg 文件編譯爲目標 .cpp 文件;
將目標 .cpp 文件加入項目,在啓用 Lua 虛擬機後調用目標文件中的 open() 函數註冊導出的內容。
看到這裏相信你們最想知道的什麼是pkg文件,pkg文件內容怎麼寫, 簡單說pkg文件就是一個遵循必定規則的文本文件,規則很簡單和咱們的頭文件差很少,只是代表咱們那些類,那些函數須要暴露給lua使用,具體的規則和使用能夠去官網上學習,或者參考tolua++源碼中的例子,其餘的很少說了,直接給你們來個例子就明白了。
例1:tolua++的使用
// file: MyClass.h
#include <iostream>
class my_class
{
public:
void greet()
{
std::cout << 「Hello World!」 << std::endl;
}
};
// file: mylib.pkg 該pkg文件聲明咱們要綁定到lua中的類和類函數
$pfile 「UsingIt.h」
class my_class
{
my_class();
~my_class();
void greet();
};
利用工具tolua++.exe 生成cpp文件
>>tolua++ –n mylib –o mylib.cpp mylib.pkg
而後將生成mylib.cpp 文件加入你要編譯的工程中編譯,而且連接tolua++庫
// file: Main.cpp
#include <iostream>
#include <tulua++.h>
extern 「C」 {
#include <lua.h>
#include <luaxlib.h>
}
#include 「MyClass.h」
int main()
{
int tolua_mylib_open (lua_State*) ;聲明
lua_State* L = lua_open();
luaL_openlibs();
tolua_mylib_open(L); // 打開mylib,該函數在cpp中有實現。
luaL_dofile(L, 「mytest.lua」); // 執行腳本文件
lua_close(L);
return 0;
}
測試lua文件
//mytest.lua
local my = my_class()
my:greet()
看到這裏你們對tolua++的用法應該已經明白了吧,把上面生成cpp的步驟寫個腳本是否是更方便,還有int tolua_mylib_open (lua_State*) ;要放在咱們本身的文件聲明是否是很怪,其實解決這個問題很簡單隻要tolua++在生成一個頭文件就能夠了。咱們接下來的quick就爲咱們提供了這樣的支持,在quick上述得操做就有事一個腳本和一個.tolua的文件,而後會生成一個cpp和.h 文件,只要咱們把生成的cpp和.h加入工程就能夠了,是否是很方便了,而且quick和cocos2dx自己就提供了對lua的支持,因此咱們使用quick須要導入咱們本身的類就能夠了。就不在這裏多說,下一章咱們將詳細講解quick中的luabinding工做原理,也是咱們本篇文章的重點。
本章主要是重點是講解在quick-cocos2d-x 中將自定義C-C++ API導出給lua使用。你們會很奇怪,這片文章主要是講quick中luabinding的使用,爲何要將前兩章的內容呢,其實前兩章很是重要,只有瞭解lua註冊的具體細節,才能理解使用tolua++這個工具的必要性,同時纔會思考tolua++帶來的優缺點,而後才能理解quick中compile-luabinding.bat幫助咱們處處C-C++ API腳本的好處,才能真正瞭解怎麼使用。到目前爲止quick和cocos2d-x + lua 都是基於tolua++的。將來可能會擺脫對tolua++的依賴,反正quick團隊是這樣說的。若是你閱讀本文只是爲了學習lua和tolua的綁定原理,那麼接下來的內容就沒有必要看了,接下來內容只對使用quick-cocos2d-x進行開發的人有用。咱們進入正題。
cocos2d-x 和 quick-cocos2d-x 的底層代碼都是使用 C++ 語言開發的。爲了使用 Lua 腳本語言進行開發,咱們利用 tolua++ 工具,將大量的 C/C++ API 導出到了 Lua 中。只是各自進行實現了一下腳本幫助cocos2d-x和quick的使用者。下面是quick中使用tolua++的基本步驟:
從 C/C++ 源代碼複製頭文件的內容到 .tolua(tolua++ 文檔中稱爲 .pkg)文件中。
修改 .tolua 文件內容,去掉 tolua++ 沒法識別的內容,以及不須要導出到 Lua 的定義。
運行 tolua++ 工具,根據 .tolua 文件生成 luabinding 接口文件(由 .cpp 文件和 .h 文件自稱)。
在 AppDelegate.cpp 中加載 luabinding 文件。
在 AppDelegate 初始化 Lua 虛擬機後,調用 luabinding 接口文件中的 luaopen 函數,註冊 C/C++ API。
咱們經過實際的例子先來演示quick中tolua++的使用,而後再去解釋其細節。
建立一個quick-cocos2d-x 3.3rc1的工程,打其目錄,看看咱們須要在哪裏進行操做。如建立一個名爲game3.3rc1的工程。其目錄結構以下。
咱們須要要關注的目是:
在這個目錄中咱們能夠看到兩個腳本,還有一些.tolua的文件,以及一些只是擴展名不一樣的.h和.cpp文件。對的,你猜對了。這些tolua文件就是pkg文件,對應的.h和.cpp就是對應的使用tolua++生成的。這個腳本看其名字就應該知道是幹什麼的吧。固然是看不出來,咱們先來看一下這個腳本build.bat的內容:
這個腳本的功能就是講剛剛的.tolua文件轉換成對應的.和.cpp文件,還指定了一些參數。對於腳本compile_luabinding.bat 實際上就是封裝了tolua++的使用,其實其下層腳本是用php封裝的,有興趣的讀者能夠去研究一些封裝的細節。對應使用者咱們只須要搞清楚這幾個參數的意義而後依葫蘆畫瓢就能夠。
compile-luabinding.bat 腳本使用參數的意義
從上面build.bat 中咱們能夠知道將.tolua文件轉換成.h和cpp的實際上compile_luabinding這個腳本的,下面對其參數進行說明:
-prx cc ,將這些API或者類導入到lua後使用前綴
-d ,指定生成cpp和h文件存放的路徑
-E,指定某個類須要按照某種方式處理,不然可能會出現內存泄露等問題。
CCObject 及其繼承類都具有「引用計數」和「自動釋放」機制。若是你的 C++ 對象是從 CCObject 繼承的,那麼必須告訴 tolua++ 作相應處理,不然可能出現內存泄漏等問題。
對於 quick,只須要在 build 腳本中經過 -E CCOBJECTS 參數指定這些 class 的名字便可。
若是有多個類,那麼每一個類名之間用「,」分隔便可
ompile-luabinding.bat -E CCOBJECTS=MyClass,MyClass2,MyClass3 -d "$OUTPUT_DIR" MyClass_luabinding.tolua
從C/C++源文件編寫一個.tolua++文件
假設咱們的 MyClass.h 頭文件內容以下:
#ifndef __MY_CLASS_H_
#define __MY_CLASS_H_
class MyClass :public CCObject
{
public:
static void addTwoNumber(float number1, float number2);
private:
MyClass(void) {}
};
#endif // __MY_CLASS_H_
爲了便於維護,應該將 .h 文件對應的 tolua 命名爲 XXX_luabinding.tolua。這樣生成的 luabinding 接口文件名就是 XXX_luabinding.cpp 和 XXX_luabinding.h,不會和已有的 C/C++ 源文件衝突。
建立 MyClass_luabinding.tolua 文件,詳細的內容修改規則,會在本文後續部分說明。並修改內容爲:
class MyClass : public CCObject
{
public:
static void addTwoNumber(float number1, float number2);
};
生成 luabinding 接口文件
在剛剛的build.bat腳本中添加下一行,或者本身新建一個腳本:
call %MAKE_LUABINDING% -E MyClass –pfx my -d %OUTPUT_DIR% MyClass_luabinding.tolua
若是一切順利:
creating file: MyClass_luabinding.cpp
creating file: MyClass_luabinding.h
// add to AppDelegate.cpp
#include "MyClass_luabinding.h"
// add to AppDelegate::applicationDidFinishLaunching()
CCLuaStack* stack = CCScriptEngineManager::sharedManager()
->getScriptEngine()
->getLuaStack();
lua_State* L = stack->getLuaState();
luaopen_MyClass_luabinding(L);
載入 luabinding 接口文件
打開咱們的項目,將 MyClass_luabinding.cpp 和 MyClass_luabinding.h 文件加入工程。而後修改 AppDelegate.cpp 文件:
在 AppDelegate.cpp 頭部區域添加:
#include "MyClass_luabinding.h"
在 AppDelegate::applicationDidFinishLaunching() 函數內添加:
luaopen_MyClass_luabinding(L);
注意這一行代碼應該添加在其餘 luaopen 函數後面,例如:
// register lua engine
CCLuaEngine *pEngine = CCLuaEngine::defaultEngine();
CCScriptEngineManager::sharedManager()->setScriptEngine(pEngine);
CCLuaStack *pStack = pEngine->getLuaStack();
lua_State* L = pStack->getLuaState();
// load lua extensions
luaopen_lua_extensions(L);
// load cocos2dx_extra luabinding
luaopen_cocos2dx_extra_luabinding(L);
// thrid_party
luaopen_third_party_luabinding(L);
// CCBReader
tolua_extensions_ccb_open(L);
// MyClass
luaopen_MyClass_luabinding(L);
通過上述修改後,從新編譯運行項目應該就能夠在 Lua 腳本中使用咱們導出的 MyClass 對象極其方法了。
在lua中使用導出的類
// test.lua
my.MyClass.addTwoNumber(1,2);
前面的 MyClass 是一個很是簡單的例子,但咱們實際遊戲中的 C/C++ API 可能比較複雜。在修改 .tolua 文件內容時,應該仔細閱讀如下內容。
導出的 API 越多,在 Lua 虛擬機中佔用的符號表空間就越多。所以咱們第一步要作的就是刪除全部無需在 Lua 中使用的內容。
對於 enum、宏定義,若是須要導出,原文保留便可。但宏定義只能導出數值定義,例如:
define kCCHTTPRequestMethodGET 0
#define kCCHTTPRequestMethodPOST 1
而非數值的宏定義沒法導出,如下內容會導出失敗:
#define kMyConstantString "HELLO"
刪除全部沒法識別的宏,例如 CC_DLL。
刪除 C++ class 中全部非 public 的定義。
刪除 C++ class 中的類成員變量
刪除 inline 關鍵詞,以及 inline function 的實現,只保留聲明。
CCObject 及其繼承類都具有「引用計數」和「自動釋放」機制。若是你的 C++ 對象是從 CCObject 繼承的,那麼必須告訴 tolua++ 作相應處理,不然可能出現內存泄漏等問題。
對於 quick,只須要在 build 腳本中經過 -E CCOBJECTS 參數指定這些 class 的名字便可。
例如前面 MyClass 的示例中,用 -E CCOBJECTS=MyClass 告訴 tolua++ 應該將 MyClass 看成 CCObject 的繼承類進行處理。
若是有多個類,那麼每一個類名之間用「,」分隔便可,例如:
ompile-luabinding.bat -E CCOBJECTS=MyClass,MyClass2,MyClass3 -d "$OUTPUT_DIR" MyClass_luabinding.tolua
有些宏是不能直接刪除的,例如 CC_PROPERTY。對於這類宏,須要根據宏定義,將宏展開爲聲明。
CC_PROPERTY(float, m_fDuration, Duration)
展開爲:
float getDuration();
void setDuration(float v);
須要如此處理的宏包括:CC_PROPERTY_READONLY, CC_PROPERTY, CC_PROPERTY_PASS_BY_REF,CC_SYNTHESIZE_READONLY,CC_SYNTHESIZE_READONLY_PASS_BY_REF,CC_SYNTHESIZE,CC_SYNTHESIZE_PASS_BY_REF,CC_SYNTHESIZE_RETAIN。
幸運的是這些宏大多隻用在 cocos2d-x 基礎代碼裏,咱們本身的 C++ class 仍是不要用這些宏了。
若是使用了名字空間,那麼在 .tolua 的頭部應該加入:
$using namespace myname;
這裏用到的「$」符號,後續內容會原樣放入 luabinding 文件。
若是生成的 luabinding 接口文件沒法編譯,須要檢查是不是須要 include 相應的頭文件,並添加以下代碼:
$using namespace myname; $#include "MyClass.h"
一些函數的參數或返回值,使用了 const 修飾符。因爲 tolua++ 的限制,並不能很好的處理這類定義,因此咱們要從 .tolua 文件中移除 const 修飾符。惟一例外的就是 const char* 不須要修改成 char*。
例如:
CCPoint convertToNodeSpace(const CCPoint& worldPoint);
應該修改成:
CCPoint convertToNodeSpace(const CCPoint& worldPoint);
這樣修改的緣由是 tolua++ 把 const CCPoint 和 CCPoint 當作兩個不一樣的類型來處理。若是不作修改,那麼調用函數時會報告參數類型不符。
若是一個函數的全部參數都是引用或指針類型,而且不是 const char*,那麼在 luabinding 接口文件中,該函數會返回多個值。
例如:
void getPosition(float* x = 0, float* y = 0);
在 Lua 中調用這個函數,會獲得兩個返回值:
local x, y = node:getPosition()
quick 裏,容許將 Lua 函數傳入 C/C++,只要求 C/C++ 函數中使用 int 作參數類型。但在 .tolua 文件裏,則必須使用 LUA_FUNCTION 作參數類型。
例如:
static CCHTTPRequest* createWithUrlLua(int listener,
const char* url,
int method = kCCHTTPRequestMethodGET);
listener 參數用於保存傳入的 Lua 函數,因此 .tolua 文件裏要改寫爲:
static CCHTTPRequest* createWithUrlLua(LUA_FUNCTION listener,
const char* url,
int method = kCCHTTPRequestMethodGET);
具體用法請參考 lib/cocos2dx_extra/extra/network/CCHTTPRequest 中的 createWithUrlLua() 方法。
要從 C/C++ 返回二進制數據給 Lua,函數返回值類型必須是 int,而 .tolua 文件中修改返回值爲 LUA_STRING。函數中,須要用 CCLuaStack::pushString() 將二進制數據放入 Lua stack。而後返回「須要傳遞給 Lua 的值」的數量。
具體用法請參考 lib/cocos2dx_extra/extra/network/CCHTTPRequest 中的 getResponseDataLua() 方法。
從 Lua 傳遞二進制數據給 C/C++ 很簡單,使用 const char* 參數類型和 int 類型參數分別指定二進制數據的指針和數據長度。
具體用法請參考 lib/cocos2dx_extra/extra/crypto/CCCrypto 中的 decryptXXTEALua() 方法。
關於利用 tolua++ 的更多用法,建議參考 lib/cocos2dx_extra 中的 CCCrypto、CCNative、CCHTTPRquest 等 class。這些 class 對 Lua 提供了良好的支持,具體用法上也覆蓋了絕大多數 C/C++ 和 Lua 交互的需求。