发布日期:2016-02-29 11:13 来源: 标签: 编程语言 C++开发语言 C++类和函数 C++Cocos2d-x
本章我们主要学习Cocos2d-x下Lua调用自定义C++类和函数,下面们就做一下具体讲解,希望大家多多支持中国站长网络学院。
第一层:纯C环境下,把C函数注册进Lua环境
建立一个a.lua和一个a.c文件,内容如下,一看就明白是怎么回事了:
a.lua
print(foo(99))
a.c
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
int foo(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, "foo", foo);
  luaL_dofile(L, "a.lua");
  lua_close(L);
  return 0;
}
在命令行下用gcc来编译并执行吧:
gcc a.c -llua && ./a.out
注意-llua选项是必要的,因为要连接lua的库。看完上面那段代码,再解释起来就容易多了:
1、要想注册进Lua环境,函数需要定义为这个样:int xxx(lua_State *L)
2、使用lua_tonumber、lua_tostring等函数,来取得传入的参数,比如lua_tonumber(L, 1)就是得到传入的第一个参数,且类型为数字
3、使用lua_pushnumber、lua_pushstring等函数,来将返回值压入Lua的环境中,因为Lua支持函数返回多个值,所以可以push多个返回值进Lua环境
4、最终函数返回的数字表示有多少个返回值被压入了Lua环境
5、使用lua_register宏定义来将这个函数注册进Lua环境,Lua脚本里就可以用它了
第二层:在cocos2d-x环境下,把C函数注册进Lua环境
也简单:
1、在frameworks/runtime-src/Classes/目录下,找到AppDelegate.cpp文件。如果frameworks目录不存在,则需要参考这篇Blog:用Cocos Code IDE写Lua,如何与项目中的C++代码和谐相处
AppDelegate.cpp文件中的关键代码如下:
```c++
auto engine = LuaEngine::getInstance();
ScriptEngineManager::getInstance()->setScriptEngine(engine);


也可以通过ScriptEngineManager类从头取得当前的LuaEngine对象,然后再getLuaStack()方法得到封装的LuaStack对象,再调用getLuaState()得到原始的lua_State结构指针。只要知道了入口位置,其他一切就不成问题了,还是挺简单的。
感兴趣的话可以去看一下ScriptEngineManager类的详细定义,在frameworks/cocos2d-x/cocos/base/CCScriptSupport.h文件中。
BTW:这里还有一个小知识点,插入在AppDelegate.cpp中的自定义代码尽量写在COCOS2D_DEBUG宏定义的判断前面,因为在调试环境下和真机环境下后续执行的代码是不一样的:

2、接下来,找个地方把test_lua_bind函数定义写进去就算大功告成了。如果追求文件组织的优雅,按理说应该新建一个.c文件,但这样的话搞不好会把自己陷入到编译阶段的泥潭里,所以先不追求优雅,而就在AppDelegate.cpp文件末尾写上函数的定义就可以了,简单清楚明了:
int test_lua_bind(lua_State *L)
{
    int number = lua_tonumber(L, 1);
    number = number + 1;
    lua_pushnumber(L, number);
    return 1;
}
3、大功告成,现在就可以在main.lua文件里使用test_lua_bind()函数了:
  local i = test_lua_bind(99)
  print("lua bind: " .. tostring(i))
4、如果是新建一个.c文件呢?把AppDelegate.cpp文件里test_lua_bind函数定义的代码删掉,在头部#include后面加入:
#include "test_lua_bind.h"
在frameworks/runtime-src/Classes目录下创建test_lua_bind.h文件,内容如下:
extern "C" {
#include "lua.h"
#include "lualib.h"
}
int test_lua_bind(lua_State *L);
再创建test_lua_bind.c文件,内容不变:
#include "test_lua_bind.h"
int test_lua_bind(lua_State *L)
{
    int number = lua_tonumber(L, 1);
    number = number + 1;
    lua_pushnumber(L, number);
    return 1;
}
此时用cocos compile -p mac命令编译,会发现test_lua_bind.c文件并没有被编译。这是当然的,普通的C/C++项目都是用Makefile来指定编译哪些.c/cpp文件的,当前的cocos2d-x项目虽然没有Makefile文件,但也是遵循这个原则的,也即肯定是有一个地方来指定所有要编译的文件的,需要在这个地方把test_lua_bind.c加进去,使得整个项目编译时把它也作为项目的一部分。
答案是,cocos2d-x项目没有使用Makefile,而是非常聪明地使用了与具体环境相关的工程文件来作为命令行编译的环境,比如在编译iOS或Mac时就使用Xcode工程文件,在编译Android时就使用Android.mk文件。
所以,添加好了test_lua_bind.h和test_lua_bind.c文件后,用Xcode打开项目,将这俩文件添加进工程中就行了。

注意,千万不要勾选“Copy items into destination group's folder(if needed)”,因为cocos2d-x的Xcode工程目录组织不是常规的结构,一旦勾选这个,会导致这两个文件被拷贝至frameworks/runtime-src/proj.ios_mac目录下,原来frameworks/runtime-src/Classes目录下的文件就废掉了,这样的组织方式会乱,而且会影响Android那边对这俩文件的引用。
把test_lua_bind.h和test_lua_bind.cpp这俩文件添加进Xcode工程后,再去命令行执行cocos compile -p mac,编译就能成功了。
网上有其他文章说还要修改Xcode工程的“User Headers Path”,这个经过试验是不需要的,哪怕把这俩文件放进新建的文件夹里也不需要,只要加入了Xcode工程即可,因为Xcode内部根本就不是按照文件夹的形式来组织文件的,它自己有一套叫做“Group”的东西。搞了好几年iOS开发,对Xcode的这个特性还是熟悉的。

第三层:了解为什么要使用toLua++来注册C++类
因为Lua的本质是C,不是C++,Lua提供给C用的API也都是基于面向过程的C函数来用的,要把C++类注册进Lua形成一个一个的table环境是不太容易一下子办到的事,因为这需要绕着弯地把C++类变成各种其他类型注册进Lua,相当于用面向过程的思维来维护一个面向对象的环境。这其中的细节就不去深究了,总之正是因为如此,所以单纯地手写lua_register()等代码来注册C++类是行不通的、代价高昂的,所以需要借助toLua++这个工具。
这一层的知识点看似简单,但其实是非常重要的,只有理解了手工用lua_register()去注册C++类的难度,才能理解使用toLua++这类工具的必要性。只有理解了使用toLua++工具的必要性,才会潜下心来冷静地接受toLua++本身的优点和缺点。只有看到了toLua++本身的缺点和使用上的麻烦,才会真心理解cocos2d-x使用bindings-generator脚本带来的好处。只有理解了bindings-generator脚本带来的好处,才能谅解这个脚本本身在使用上的一些不便之处。
第四层:在纯C++环境下,使用toLua++来把一个C++类注册进Lua环境
虽然终极方法是用bindings-generator脚本来注册C++类进cocos2d-x的Lua环境,但理解toLua++本身的用法还是狠有必要的,只有知道了toLua++原本的用法,才能更好地理解cocos2d-x是怎么把自己的C++类都注册进Lua环境的,这不仅能让编程时的思路更加清晰,也能为日后在源码中寻找各种接口文档的过程中不至于看不懂那一大堆tolua_beginmodule、tolua_function是什么意思。影响程序员学习提高的一大障碍就是忽略那些一知半解的代码,不去刨根究底地搞明白。
使用toLua++的标准做法是:
1、准备好自己的C++类,该怎么写就怎么写
2、仿造这个类的.h文件,改一个.pkg文件出来,具体格式要按照toLua++的规定,比如移除所有的private成员等
3、建一个专门用来桥接C++和Lua之间的C++类,使用特殊的函数签名来写它的.h文件,.cpp文件不写,等着toLua++来生成
4、给这个桥接的C++类写一个.pkg文件,按照toLua++的特殊格式来写,目的是把真正做事的C++类给定义进去
5、在命令行下用toLua++生成桥接类的.cpp文件
6、程序入口引用这个桥接类,执行生成的桥接函数,Lua环境中就可以使用真正做事的C++类了





相关评论

专题信息
    Visual C++是一个功能强大的可视化软件开发工具,是高等院校计算机及相关专业主要核心课程。 本教程对Visual C++ 的应用与开发进行了详细系统的介绍,内容主要包括:Visual C++程序的建立,菜单、工具栏和状态栏的创建,对话框和常用控件,窗口、文档与视图,图形绘制,数据库应用,多媒体技术等。 本教程以案例教学为主,各章节都附有大量的实例,并且操作步骤详细,有利于引导读者更好的消化、理解和实际应用本章节所学的知识内容,希望大家能多多支持中国站长网络学院!