cocos进阶教程(1)Lua调用自定义C++类和函数的最佳实践

第一层:纯C环境下,把C函数注册进Lua环境

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;
}

看完上面那段代码,再解释起来就容易多了:

1、要想注册进Lua环境,函数需要定义为这个样:int xxx(lua_State *L)
2、使用lua_tonumberlua_tostring等函数,来取得传入的参数,比如lua_tonumber(L, 1)就是得到传入的第一个参数,且类型为数字
3、使用lua_pushnumberlua_pushstring等函数,来将返回值压入Lua的环境中,因为Lua支持函数返回多个值,所以可以push多个返回值进Lua环境
4、最终函数返回的数字表示有多少个返回值被压入了Lua环境
5、使用lua_register宏定义来将这个函数注册进Lua环境,Lua脚本里就可以用它了,大功告成!就这么简单!

第二层:在cocos2d-x环境下,把C函数注册进Lua环境

1、在AppDelegate.cpp文件中的关键代码如下:

LuaStack* stack = engine->getLuaStack();
LuaStack* stack = engine->getLuaStack();
lua_State *L = stack->getLuaState();

lua_register(L, "test_lua_bind", test_lua_bind);

2、接下来,找个地方把test_lua_bind函数定义写进去就算大功告成了。如果追求文件组织的优雅,按理说应该新建一个.c文件。

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;
}

cocos2d-x项目没有使用Makefile,而是非常聪明地使用了与具体环境相关的工程文件来作为命令行编译的环境,比如在编译iOS或Mac时就使用Xcode工程文件,在编译Android时就使用Android.mk文件。

所以,添加好了test_lua_bind.htest_lua_bind.c文件后,用Xcode打开项目,将这俩文件添加进工程中就行了。

test_lua_bind.htest_lua_bind.cpp这俩文件添加进Xcode工程后,再去命令行执行cocos compile -p mac,编译就能成功了。

第四层:在纯C++环境下,使用toLua++来把一个C++类注册进Lua环境

使用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++类了

toLua++这种自己手写.pkg文件的方式古老又难受,所以我没有仔细地去学习,这套流程放在10年前的那个年代是没有太大问题的,作者怎么规定就怎么用好了,但是放在2014年的今天,任何程序的架构设计都讲究学习成本低、轻量化、符合以往的习惯,因此toLua++用起来我觉得其实是难受的。

下面我以尽量最少的代码来走一遍toLua++的流程,注意这是在纯C++环境下,跟任何框架都没关系,也不考虑内存释放等细节:

MyClass.h

class MyClass {
public:
  MyClass() {};

  int foo(int i);
};

MyClass.cpp

#include "MyClass.h"

int MyClass::foo(int i)
{
  return i + 100;
}

MyClass.pkg

class MyClass
{
  MyClass();
  int foo(int i);
};

MyLuaModule.h

extern "C" {
#include "tolua++.h"
}

#include "MyClass.h"

TOLUA_API int tolua_MyLuaModule_open(lua_State* tolua_S);

MyLuaModule.pkg

$#include "MyLuaModule.h"

$pfile "MyClass.pkg"

main.cpp

extern "C" {
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}

#include "MyLuaModule.h"

int main()
{
  lua_State *L = lua_open();

  luaL_openlibs(L);

  tolua_MyLuaModule_open(L);

  luaL_dofile(L, "main.lua");

  lua_close(L);

  return 0;
}

main.lua

local test = MyClass:new()
print(test:foo(99))

先在命令行下执行:

tolua++ -o MyLuaModule.cpp MyLuaModule.pkg

此命令用来生成桥接文件MyLuaModule.cpp。注意命令行中-o参数的顺序不能随意摆放,从这个小事也能看出tolua++的古老和难用

生成好MyLuaModule.cpp文件后,就能看到它里面的那一大堆桥接代码了,比如tolua_beginmoduletolua_function等。以后看到这些东西就不陌生了,就明白这些函数只是toLua++用来做桥接的必备代码了,简单看一下代码,就理解toLua++是怎样把MyClass这个C++类注册进Lua中的了:

至此,对toLua++的运作原理心里就透亮了,无非就是:

1、把自己该写的类写好
2、写个.pkg文件,告诉toLua++这个类暴露出哪些接口给Lua环境
3、再写个桥接的.h和.pkg文件,让toLua++去生成桥接代码
4、在程序里使用这个桥接代码,类就注册进Lua环境里了

第五层:使用cocos2d-x的方式来将C++类注册进Lua环境

bindings-generator脚本的工作机制是:

1、不用挨个类地写桥接.pkg和.h文件了,直接定义一个ini文件,告诉脚本哪些类的哪些方法要暴露出来,注册到Lua环境里的模块名是什么,就行了,等于将原来的每个类乘以3个文件的工作量变成了所有类只需要1个.ini文件
2、摸清了toLua++工具的生成方法,改由Python脚本动态分析C++类,自动生成桥接的.h和.cpp代码,不调用tolua++命令了
3、虽然不再调用tolua++命令了,但是底层仍然使用toLua++的库函数,比如tolua_function,bindings-generator脚本生成的代码就跟使用toLua++工具生成的几乎一样

bindings-generator脚本掌握了生成toLua++桥接代码的主动权,不仅可以省下大量的.pkg和.h文件,而且可以更好地插入自定义代码,达到cocos2d-x环境下的一些特殊目的,比如内存回收之类的。所以cocos2d-x从3.x开始放弃了toLua++和.pkg而改用了自己写的bindings-generator脚本是非常值得赞赏的聪明做法。

接下来说怎么用bindings-generator脚本:

1、写自己的C++类,按照cocos2d-x的规矩,继承cocos2d::Ref类,以便使用cocos2d-x的内存回收机制。当然不这么干也行,但是不推荐,不然在Lua环境下对象的释放狠麻烦。
2、编写一个.ini文件,让bindings-generator可以根据这个配置文件知道C++类该怎么暴露出来
3、修改bindings-generator脚本,让它去读取这个.ini文件
4、执行bindings-generator脚本,生成桥接C++类方法
5、用Xcode将自定义的C++类和生成的桥接文件加入工程,不然编译不到
6、修改AppDelegate.cpp,执行桥接方法,自定义的C++类就注册进Lua环境里了

看着步骤挺多,其实都狠简单。下面一步一步来。

首先是自定义的C++类。我习惯将文件保存在frameworks/runtime-src/Classes/目录下:

frameworks/runtime-src/Classes/MyClass.h

#include "cocos2d.h"

using namespace cocos2d;

class MyClass : public Ref
{
public:
  MyClass()   {};
  ~MyClass()  {};
  bool init() { return true; };
  CREATE_FUNC(MyClass);

  int foo(int i);
};

frameworks/runtime-src/Classes/MyClass.cpp

#include "MyClass.h"

int MyClass::foo(int i)
{
  return i + 100;
}

然后编写.ini文件。在frameworks/cocos2d-x/tools/tolua/目录下能看到genbindings.py脚本和一大堆.ini文件,这些就是bindings-generator的实际执行环境了。随便找一个内容比较少的.ini文件,复制一份,重新命名为MyClass.ini。大部分内容都可以凑合不需要改,这里仅列出必须要改的重要部分:

frameworks/cocos2d-x/tools/tolua/MyClass.ini

[MyClass]
prefix           = MyClass
target_namespace = my
headers          = %(cocosdir)s/../runtime-src/Classes/MyClass.h
classes          = MyClass

也即在MyClass.ini中指定MyClass.h文件的位置,指定要暴露出来的类,指定注册进Lua环境的模块名。

注意,这个地方我踩了个坑。如果.ini配置文件中存在macro_judgement = ...宏定义,要特别小心,我第一次是从cocos2dx_controller.ini文件复制来的,结果没注意macro_judgement,导致生成的桥接类文件加入了不该加入的宏,只在iOS和Android平台上才起作用,对Mac平台无效,这个要特别注意。

然后修改genbindings.py文件129行附近,将MyClass.ini文件加进去:

frameworks/cocos2d-x/tools/tolua/genbindings.py

cmd_args = {‘cocos2dx.ini‘ : (‘cocos2d-x‘, ‘lua_cocos2dx_auto‘),             ‘MyClass.ini‘ : (‘MyClass‘, ‘lua_MyClass_auto‘),             ...

(其实这一步本来是可以省略的,只要让genbindings.py脚本自动搜寻当前目录下的所有ini文件就行了,不知道将来cocos2d-x团队会不会这样优化)

至此,生成桥接文件的准备工作就做好了,执行genbindings.py脚本:

python genbindings.py

(在Mac系统上可能会遇到缺少yaml、Cheetah包的问题,安装这些Python包狠简单,先sudo easy_install pip,把pip装好,然后用pip各种pip searchsudo pip install就可以了)

成功执行genbindings.py脚本后,会在frameworks/cocos2d-x/cocos/scripting/lua-bindings/auto/目录下看到新生成的文件:

每次执行genbindings.py脚本时间都挺长的,因为它要重新处理一遍所有的.ini文件,建议大胆修改脚本文件,灵活处理,让它每次只处理需要的.ini文件就可以了,比如像这个样子:

frameworks/cocos2d-x/cocos/scripting/lua-bindings/auto/目录下观察一下生成的C++桥接文件lua_MyClass_auto.cpp,里面的注册函数名字为register_all_MyClass(),这就是将MyClass类注册进Lua环境的关键函数:

编辑frameworks/runtime-src/Classes/AppDelegate.cpp文件,首先在文件头加入对lua_MyClass_auto.hpp文件的引用:

然后在正确的代码位置加入对register_all_MyClass函数的调用:

最后在执行编译前,将新加入的这几个C++文件都加入到Xcode工程中,使得编译环境知道它们的存在:

这其中还有一个小坑,由于lua_MyClass_auto.cpp文件要引用MyClass.h文件,而这俩文件分属于不同的子项目,互相不认识头文件的搜寻路径,因此需要手工修改一下cocos2d_lua_bindings.xcodeproj子项目的User Header Search Paths配置。特别注意一共有几个../

最后,就可以用cocos compile -p mac命令重新编译整个项目了,不出意外的话编译一定是成功的。

修改main.lua文件中,尝试调用一下MyClass类:

local test = my.MyClass:create()
print("lua bind: " .. test:foo(99))

register_all_cocos2dx_xxx(lua_State* tolua_S) 函数的

tolua_module(tolua_S,nullptr,0);
tolua_beginmodule(tolua_S,nullptr);

下面增加包名

tolua_module(tolua_S,"xxx",0);
tolua_beginmodule(tolua_S,"xxx");

结尾处多写一条

tolua_endmodule(tolua_S);

2. 接上面增加了lua包名后,生成binding文件会报错

conversion wasn‘t set in ‘ns_map‘ section of the conversions.yaml

修改 frameworks/cocos2d-x/tools/bindings-generator/targets/conversions.yaml

在 ns_map下增加一条新的包名信息

"c++命名空间::": "lua包名."

时间: 2024-10-11 21:51:25

cocos进阶教程(1)Lua调用自定义C++类和函数的最佳实践的相关文章

Cocos2d-x下Lua调用自定义C++类和函数的最佳实践 -- 转

原地址: http://blog.segmentfault.com/hongliang/1190000000631630 Cocos2d-x下Lua调用自定义C++类和函数的最佳实践 关于cocos2d-x下Lua调用C++的文档看了不少,但没有一篇真正把这事给讲明白了,我自己也是个初学者,摸索了半天,总结如下: cocos2d-x下Lua调用C++这事之所以看起来这么复杂.网上所有的文档都没讲清楚,是因为存在5个层面的知识点: 1.在纯C环境下,把C函数注册进Lua环境,理解Lua和C之间可以

【转】Cocos2d-x下Lua调用自定义C++类和函数的最佳实践

转自:http://segmentfault.com/blog/hongliang/1190000000631630 关于cocos2d-x下Lua调用C++的文档看了不少,但没有一篇真正把这事给讲明白了,我自己也是个初学者,摸索了半天,总结如下: cocos2d-x下Lua调用C++这事之所以看起来这么复杂.网上所有的文档都没讲清楚,是因为存在5个层面的知识点: 1.在纯C环境下,把C函数注册进Lua环境,理解Lua和C之间可以互相调用的本质 2.在cocos2d-x项目里,把纯C函数注册进L

Cocos2d-x下Lua调用自定义C++类和函数的最佳实践

原文地址:http://segmentfault.com/a/1190000000631630 关于cocos2d-x下Lua调用C++的文档看了不少,但没有一篇真正把这事给讲明白了,我自己也是个初学者,摸索了半天,总结如下: cocos2d-x下Lua调用C++这事之所以看起来这么复杂.网上所有的文档都没讲清楚,是因为存在5个层面的知识点: 1.在纯C环境下,把C函数注册进Lua环境,理解Lua和C之间可以互相调用的本质2.在cocos2d-x项目里,把纯C函数注册进Lua环境,理解cocos

Lua调用自定义C++类

弄了一天终于会Lua调用自定义C++类.不容易啊. 我的电脑是64位的,装了64的Python不行,装了32位的就可以了,靠!下面是报错信息 python pyyaml Cheetah全都是装32位的,其中python版本是用2.7.8,是可以的. Cocos2d-x v3.2 Cocos Code IDE 1.2.0 NDK r9d dos2unix,windows下可能在执行脚本时有这个错误.是创建项目时没有了这个文件,可以去cocos2dx里面复制一个,如 E:\cocos2d-x-3.2

CC3.2+Lua(8) ——Lua调用自定义C++类

[唠叨] 本节要讲的是如果将自己写的C++类注册进Lua环境,让Lua去调用自定义的C++类. 网上有很多都是用原始的tolua++工具来注册C++类的,我看了很多这样的教程,感觉操作起来十分麻烦,而且也很难看懂他们到底在讲什么. 其实,在cocos2dx v3.2版本中,提供了bindings-generator脚本来封装toLua++的用法,从而节省了工作量. [致谢] http://segmentfault.com/blog/hongliang/1190000000718145 (讲得非常

lua创建自定义的类

-- 使用lua自定义类 local _class = {} function BaseClass(super) -- 生成一个类类型, 实际上存放类信息 local class_type = {} -- 顶层父类"__delete"为false class_type.__delete = false -- 父类初始化 class_type.super = super -- 创建接口(类似与C++里面的构造函数) class_type.New = function(...) print

百度地图Api进阶教程-创建标注和自定义标注3.html

<!DOCTYPE html> <html> <head> <meta name="viewport" content="initial-scale=1.0, user-scalable=no" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>1

进阶教程(11)- 自定义指南针

为什么说是自定义指南针,这是因为指南针的heading值(即是控制指南针正确朝向的数值)是由我们定义的.真正的指南针,其方向就是指北,你永远都不需要指定heading值,像krpano自身也有指南针插件,它是直接调用手机上的指南针来找出对应的方位,无需人工干预.但事实上我还没遇到过国内有真正把指南针插件拿来商用的案例. 但在自定义地图中.例如房地产.旅游景区中,对朝向.方向的指示是有迫切需求的.但不可能用移动设备上的指南针,因此一个虚拟的指南针更有利于用户体验.krpano的官方案例提供了三种指

Lua 调用自定义C模块

这是<Lua程序设计>中提到的,但是想成功执行,对于初学Lua的确没那么简单.这里涉及如何如何生成一个动态链接库so文件:Lua5.2中导出函数从LuaL_register变成了LuaL_newlib.对于具体的细节有待深入.这里的模块名是hello_lib, Lua解释器会根据名字找到对应的模块,而后执行其中的 luaopen_XXX方法. 代码: #include <math.h> #include <lua5.2/lua.h> #include <lua5.