最近在使用cocos2d-lua写一些小游戏,遇到了需要在lua中调用自定义的c++类的情况,于是花了一下午时间来踩这个坑,目前终于成功跑起来了,故在此做一个记录。
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
int test(lua_State *L) {
int n = lua_tonumber(L, 1);
lua_pushnumber(L, n + 1);
return 1;
}
int main() {
lua_State *L = lua_open();
luaL_openlibs(L);
lua_register(L, "test", test);
luaL_dofile(L, "a.lua");
lua_close(L);
return 0;
}
在test函数中,lua_tonumber(L, 1)获取了L环境中给test输入的第一个参数,因为lua与c++的交互是通过一个栈来进行的,所以函数返回之前使用了lua_pushnumber来向栈中压入一个值。注意在lua中函数返回的可以不止一个值,所以在这里也可以多次push,最后test的return值意味着此函数一共返回了多少个值。
test函数定义好之后,通过lua_register向lua环境注册这个函数,这样在lua代码中就可以对此函数进行调用了。
print(test(1))
lua调用自定义c++类
因为在实际使用中不可能只使用到静态函数,还要经常使用自己定义的c++类,如果直接使用类似于lua_register的方式来注册c++类,那么工作量会非常大。这时候可以使用类似于tolua++之类的第三方工具来进行注册,但是这里不再赘述,就直接讲如何使用cocos2d自带的bindings-generator工具进行注册。实现所需的c++类
首先给出一个示例用的c++类:
// MyClass.h
#include "cocos2d.h"
using namespace cocos2d;
class MyClass : public Ref {
public:
MyClass() {}
~MyClass() {}
virtual bool init() {
return true;
}
CREATE_FUNC(MyClass);
// used to test
int test(int i);
};
// MyClass.cpp
#include "MyClass.h"
int MyClass::test(int i) {
return i + 1;
}
这里应该注意一点,就是在cocos2d中的自定义类应当继承ref或者其子类,因为这样可以让cocos2d将内存管理起来,不需要自己来操心。
如果lua工程目录为LuaTest/,则将c++类的两个文件放到LuaTest/frameworks/runtime-src/Classes/下面,因为新添加这两个文件,所以也要记得修改对应的makefile,例如我是在linux上进行测试的,则修改CMakeList.txt,将参加编译的文件修改为:
set(GAME_SRC
runtime-src/proj.linux/main.cpp
runtime-src/Classes/AppDelegate.cpp
runtime-src/Classes/MyClass.cpp
)
即添加了刚刚创建的MyClass。如果是其它平台,例如android,则修改对应的Android.mk,如果是xcode的工程,则需要将文件拖入工程。
创建配置文件,并使用脚本生成注册用c++文件
接下来,需要使用bindings-generator来自动生成一个用于注册的c++文件。此时切换目录到LuaTest/frameworks/cocos2d-x/tools/tolua/下,
可以看到这里有很多.ini文件,以及一个genbindings.py文件。
.ini文件定义了从c++文件生成用于注册的c++文件的规则,而genbindings.py则是cocos2d提供的脚本,可以根据ini规则生成用于注册的c++文件。
为了注册MyClass文件,简单的话可以直接复制一个ini文件,例如cocos2dx.ini,然后对其进行修改,重点要修改的内容为:
[MyClass]
prefix = MyClass
target_namespace = custom
headers = %(cocosdir)s/../runtime-src/Classes/MyClass.h
classes = MyClass
其中target_namespace为命名空间,例如cocos2d在lua中的命名空间就为cc。headers指向我们一开始创建的原始c++文件,classes中可以声明多个类,用空格隔开;因为我们的文件中只有MyClass一个类,所以这里就只写MyClass了。
修改完,命名为MyClass.ini保存。
然后是对脚本进行修改。其实可以直接修改genbindings.py,但是因为其中涉及到太多文件,每次运行的时间可能会太长,所以这里又拷贝一份,命名为genbindings_custom.py。
打开文件,在141行左右,修改:
cmd_args = {'cocos2dx.ini' : ('cocos2d-x', 'lua_cocos2dx_auto'), \
'cocos2dx_extension.ini' : ('cocos2dx_extension', 'lua_cocos2dx_extension_auto'), \
'cocos2dx_ui.ini' : ('cocos2dx_ui', 'lua_cocos2dx_ui_auto'), \
'cocos2dx_studio.ini' : ('cocos2dx_studio', 'lua_cocos2dx_studio_auto'), \
'cocos2dx_spine.ini' : ('cocos2dx_spine', 'lua_cocos2dx_spine_auto'), \
'cocos2dx_physics.ini' : ('cocos2dx_physics', 'lua_cocos2dx_physics_auto'), \
'cocos2dx_experimental_video.ini' : ('cocos2dx_experimental_video', 'lua_cocos2dx_experimental_video_auto'), \
'cocos2dx_experimental.ini' : ('cocos2dx_experimental', 'lua_cocos2dx_experimental_auto'), \
'cocos2dx_controller.ini' : ('cocos2dx_controller', 'lua_cocos2dx_controller_auto'), \
'cocos2dx_cocosbuilder.ini': ('cocos2dx_cocosbuilder', 'lua_cocos2dx_cocosbuilder_auto'), \
'cocos2dx_cocosdenshion.ini': ('cocos2dx_cocosdenshion', 'lua_cocos2dx_cocosdenshion_auto'), \
'cocos2dx_3d.ini': ('cocos2dx_3d', 'lua_cocos2dx_3d_auto'), \
'cocos2dx_audioengine.ini': ('cocos2dx_audioengine', 'lua_cocos2dx_audioengine_auto'), \
'cocos2dx_csloader.ini' : ('cocos2dx_csloader', 'lua_cocos2dx_csloader_auto'), \
'cocos2dx_experimental_webview.ini' : ('cocos2dx_experimental_webview', 'lua_cocos2dx_experimental_webview_auto'), \
'cocos2dx_physics3d.ini' : ('cocos2dx_physics3d', 'lua_cocos2dx_physics3d_auto'), \
'cocos2dx_navmesh.ini' : ('cocos2dx_navmesh', 'lua_cocos2dx_navmesh_auto'), \
}
为:
cmd_args = {'MyClass.ini' : ('MyClass', "lua_customclass_auto")}
其意义就是根据MyClass.ini文件自动生成用于注册的lua_customclass_auto.cpp以及.hpp文件。修改完后保存退出。在命令行切换到这个目录,然后运行脚本:
$python genbindings_custom.py
这里可能碰到一个坑,要注意一下,就是,运行脚本可能会出现这样的错误:
====
Errors in parsing headers:
1. <severity = Fatal,
location = <SourceLocation file '/home/xxx/NDK/platforms/android-14/arch-arm/usr/include/android/log.h', line 70, column 10>,
details = "'stdarg.h' file not found">
====
这个错误的原因是genbindings_custom.py脚本与ndk不兼容的问题,因为找不的ndk中的llvm-3.3文件夹,如果出现这个错误,先查看自己ndk目录的toolchains文件夹,看看自己是什么版本的llvm。我这里有llvm-3.5和llvm-3.6两个相关的文件夹,而在genbindings_custom.py脚本中有这么一段:
if '3.4' in llvm_path:
config.set('DEFAULT', 'clang_version', '3.4')
else:
config.set('DEFAULT', 'clang_version', '3.3')
这一段应该是用于处理早期的ndk的脚本,我的ndk应该比较新,在这里不适用,所以应该进行修改。为了偷懒,我直接将3.3改为了3.5:
if '3.4' in llvm_path:
config.set('DEFAULT', 'clang_version', '3.4')
else:
config.set('DEFAULT', 'clang_version', '3.5')
运行脚本,现在显示成功生成了:
---------------------------------
Generating lua bindings succeeds.
---------------------------------
在AppDelegate.cpp中注册自定义的c++类
自动生成的cpp文件在LuaTest/frameworks/cocos2d-x/cocos/scripting/lua-bindings/auto/路径下,因为我们之前在脚本中设置的名字为lua_customclass_auto,所以在这里可以找到lua_customclass_auto.cpp,lua_customclass_auto.hpp两个文件。
将这两个文件拷贝到MyClass.cpp所在路径下,并且同样的在makefile中添加lua_customclass_auto.cpp文件。例如我的Linux的设置是修改CMakeList.txt:
set(GAME_SRC
runtime-src/proj.linux/main.cpp
runtime-src/Classes/AppDelegate.cpp
runtime-src/Classes/MyClass.cpp
runtime-src/Classes/lua_customclass_auto.cpp
)
生成的lua_customclass_auto文件就是脚本读取MyClass后根据其内容自动生成的用于注册的文件,打开lua_customclass_auto.hpp可以看到:
#include "base/ccConfig.h"
#ifndef __MyClass_h__
#define __MyClass_h__
#ifdef __cplusplus
extern "C" {
#endif
#include "tolua++.h"
#ifdef __cplusplus
}
#endif
int register_all_MyClass(lua_State* tolua_S);
#endif // __MyClass_h__
这里有一个register_all_MyClass方法,调用这个方法就可以向lua注册我们自定义的c++类。所以最后,打开AppDelegate.cpp,找到lua注册相关的代码,修改为:
// register lua module
auto engine = LuaEngine::getInstance();
ScriptEngineManager::getInstance()->setScriptEngine(engine);
lua_State* L = engine->getLuaStack()->getLuaState();
lua_module_register(L);
register_all_packages();
LuaStack* stack = engine->getLuaStack();
stack->setXXTEAKeyAndSign("2dxLua", strlen("2dxLua"), "XXTEA", strlen("XXTEA"));
//register custom function
//LuaStack* stack = engine->getLuaStack();
//register_custom_function(stack->getLuaState());
// 这里注册自定义的c++类
register_all_MyClass(stack->getLuaState());
并且记得在AppDelegate.cpp中include一下lua_customclass_auto.hpp文件,不然没法访问到register_all_MyClass函数。
最后,在lua代码中进行测试:
local customClass = custom.MyClass:create()
print("lua bind: "..customClass:test(100))
没错误的话就成功啦。