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 交互的需求。