【转载】https://note.youdao.com/share/?id=0f4132271151c4b62f9afb712e8304d9&type=note#/

1、在纯C环境下,把C函数注册进Lua环境,理解Lua和C之间可以互相调用的本质
2、在cocos2d-x项目里,把纯C函数注册进Lua环境,理解cocos2d-x是怎样创建Lua环境的、以及怎样得到这个环境并继续自定义它
3、了解为什么要使用toLua++来注册C++类
4、在纯C++环境下,使用toLua++来把一个C++类注册进Lua环境,理解toLua++的用法
5、在cocos2d-x项目里,使用cocos2d-x注册自身的方式把自定义的C++类注册进Lua环境,理解cocos2d-x是怎样通过bindings-generator脚本来封装toLua++的用法来节省工作量的

 

第一层:纯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标准库
lua_register(L, "foo", foo); //注册c函数到lua环境
luaL_dofile(L, "a.lua"); //执行lua脚本
lua_close(L); //关闭环境
return0;
}

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

AppDelegate.cpp文件中的关键代码如下:
auto engine = LuaEngine::getInstance();
ScriptEngineManager::getInstance()->setScriptEngine(engine);

LuaStack* stack = engine->getLuaStack();
stack->setXXTEAKeyAndSign("2dxLua", strlen("2dxLua"), "XXTEA", strlen("XXTEA")); //register custom function
//LuaStack* stack = engine->getLuaStack();
//register_custom_function(stack->getLuaState());

可以看到cocos2d-x已经为我们留出了注册自定义C函数的位置,在注释代码后面这么写就可以了:
lua_State *L = stack->getLuaState(); lua_register(L, "test_lua_bind", test_lua_bind);

也可以通过ScriptEngineManager类从头取得当前的LuaEngine对象,然后再getLuaStack()方法得到封装的LuaStack对象,再调用getLuaState()得到原始的lua_State结构指针。。

2、接下来,找个地方把test_lua_bind函数定义写进去就算大功告成了。

int test_lua_bind(lua_State *L){
int number = lua_tonumber(L, 1); number = number + 1; lua_pushnumber(L, number); return1;
}

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

普通的C/C++项目都是用Makefile来指定编译哪些.c/cpp文件的,当前的cocos2d-x项目虽然没有Makefile文件,但也是遵循这个原则的,也即肯定是有一个地方来指定所有要编译的文件的,需要在这个地方把test_lua_bind.c加进去,使得整个项目编译时把它也作为项目的一部分。

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

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

Xcode中添加.h和.cpp文件进工程

再去命令行执行cocos compile -p win,编译就能成功了。

第三层:了解为什么要使用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环境

理解toLua++本身的用法还是必要的,知道了toLua++原本的用法,才能更好地理解cocos2d-x是怎么把自己的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++的流程,注意这是在纯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 c++类的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 桥接类的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))

先在命令行下执行:(生成桥接文件的实现体MyLuaModule.cpp)

tolua++ -o MyLuaModule.cpp MyLuaModule.pkg

意命令行中-o参数的顺序不能随意摆放,

生成好MyLuaModule.cpp文件后,就能看到它里面的那一大堆桥接代码了,比如tolua_beginmoduletolua_function等。

以后看到这些东西就不陌生了,就明白这些函数只是toLua++用来做桥接的必备代码了,

简单看一下代码,就理解toLua++是怎样把MyClass这个C++类注册进Lua中的了:

toLua++生成的桥接代码

接下来,用g++来编译:

g++ MyClass.cpp MyLuaModule.cpp main.cpp -llua -ltolua++

默认就生成了a.out文件,执行,就能看到main.lua的执行结果了:

toLua++的执行结果

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

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

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

所以从cocos2d-x 3.x开始,用bindings-generator脚本代替了toLua++。

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

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

bindings-generator脚本掌握了生成toLua++桥接代码的主动权,不仅可以省下大量的.pkg和.h文件,而且可以更好地插入自定义代码,达到cocos2d-x环境下的一些特殊目的,比如内存回收之类的。

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

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

 

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

1.首先是自定义的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;
}

2.然后编写.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环境的模块名。

然后修改genbindings.pyMyClass.ini文件加进去:

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

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

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

python ./genbindings.py

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

成功执行genbindings.py后生成的桥接C++文件

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

修改genbindings.py使其只生成自定义的桥接类

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

生成的桥接文件内容

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

AppDelegate.cpp文件的头加入对lua_MyClass_auto.hpp文件的引用

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

修改AppDelegate.cpp文件,将MyClass类注册进Lua环境

如何是lua工程则在:lua_module_register.h 中添加上述调用。

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

在Xcode中加入新冒出来的C++文件

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

需要修改cocos2d_lua_bindings子项目的User Header Search Paths配置

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

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

local test = my.MyClass:create()
print("lua bind: " .. test:foo(99))
6.android上运行的话需要做的事情是要将生成的桥接文件lua_MyClass_auto.cpp放到android.mk中。

配置ini时需要注意的选项:

  • [title]:要配置将被使用的工具/ tolua的/ gengindings.py脚本的称号。一般来说,标题可以是文件名。

  • prefix:要配置一个函数名的前缀,通常,我们还可以使用文件名作为前缀 生成函数一次为前缀。

  • target_namespace:要配置在脚本层模块的名字。在这里,我们使用cc作为模块名,当你想在脚本层REF的名称,您必须将一个名为前缀,CC在名称的前面。例如,CustomClass可以参考作为cc.CustomClass

  • headers:要配置所有需要解析的头文件和%(cocosdir)s是的Cocos2d-x的引擎的根路径。

  • classes:要配置所有绑定所需的类。在这里,它支持正则表达式。因此,我们可以设置MyCustomClass。*在这里,用于查找多个特定的用法,你可以对照到tools/tolua/cocos2dx.ini

  • skip:要配置需要被忽略的功能。现在绑定发电机无法解析的void *类型,并委托类型,所以这些类型的需要进行手动绑定。而在这种情况下,你应该忽略所有这些类型,然后再手动将它们绑定。你可以对照到配置文件路径下的cocos/scripting/lua-bindings/auto 。

  • rename_functions:要配置的功能需要在脚本层进行重命名。由于某些原因,开发者希望更多的脚本友好的API,所以配置选项就是为了这个目的。

  • rename_classes:不在使用。

  • remove_prefix:不在使用。

  • base_classes_to_skip = #当被它们的子类发现的时候会跳过的基类
  • classes_have_no_parents:要配置是过滤器所需要的父类。这个选项是很少修改。

  • abstract_classes:要配置的公共构造并不需要导出的类。

  • script_control_cpp:是的。要配置脚本层是否管理对象的生命周期。如果没有,那么C++层关心他们的生命周期。 
    现在,它是不完善的,以控制原生对象的续航时间在脚本层。所以,你可以简单地把它设置为no。

c/c++导出lua绑定的更多相关文章

  1. c++自动导出lua绑定

    cocos 使用bindings-generator脚本代替了toLua++. 编写效率大大提高. 具体的在本机中分享:http://note.youdao.com/noteshare?id=0f41 ...

  2. cocos2dx的lua绑定

    一.cocos2dx对tolua++绑定的修正 A.c对lua回调函数的引用 在使用cocos2dx编写游戏时,我们经常会设置一些回调函数(时钟.菜单选择等).如果采用脚本方式编写游戏的话,这些回调函 ...

  3. Cocos2d-x v3.3 lua绑定c++类方法总结

    网上有很多cocos2d-x lua绑定c++类的接口教程,这篇文章也是总结他们的经验. 其中重点参考了 http://cn.cocos2d-x.org/tutorial/show?id=1295, ...

  4. 开源基于lua gc管理c++对象的cocos2dx lua绑定方案

    cocos2dx目前lua对应的c++对象的生命周期管理,是基于c++析构函数的,也就是生命周期可能存在不一致,比如c++对象已经释放,而lua对象还存在,如果这时候再使用,会有宕机的风险,为此我开发 ...

  5. cocos2d-x lua绑定解析

    花了几天时间看了下cocos2d-x lua绑定那块,总算是基本搞明白了,下面分三部分解析lua绑定: 一.lua绑定主要用到的底层函数 lua绑定其本质就是有一个公用的lua_Stack来进行C和L ...

  6. cocos2dx lua 绑定之二:手动绑定自定义类中的函数

    cococs2dx 3.13.1 + vs2013 + win10 1.首先按照<cocos2dx lua 绑定之一:自动绑定自定义类>绑定Student类 2.在Student类中增加一 ...

  7. 记录——excel导出lua工具(python实现)

    项目需要一个从excel导出lua配置表的工具,之前的工具是主程写的,效率极差,i7 CPU 一次全部导出要花掉1个多小时.匪夷所思的是,这么渣的效率,居然用了整整一年.当 然,中途有人反映效率差,主 ...

  8. 使用cocos2d脚本生成lua绑定

    这几天要老大要求把DragonBones移到cocos2dx 3.0 里边,并且绑定lua使用接口.因为刚学lua,使用的引擎也刚从2.2改为3.0,各种不熟悉,折腾了好几天才弄完,有空了总结一下 这 ...

  9. cocos2dx lua 绑定之一:自动绑定自定义类中的函数

    cococs2dx 3.13.1 + vs2013 + win10 1.首先定义C++类Student 在cocos2d-x\cocos文件夹下新建一个user_define的文件夹放置两个文件. 注 ...

随机推荐

  1. zoj3988 Prime Set

    思路不难想到二分图求个最大匹配P,若P>=K,则2*K即可,否则应为P*2+min(K-P,未匹配且有度数不为0的顶点个数s).但坑点在于有1的情况,所以如果直接建二分图去跑最大匹配会因为1的影 ...

  2. [bzoj1031][JSOI2007]字符加密Cipher_后缀数组

    字符加密Cipher bzoj-1031 JSOI-2007 题目大意:题目链接. 注释:略. 想法: 后缀数组裸题啊. 后缀数组其实背下来板子之后有几个数组记住就可以了. $sa_i$表示排名为$i ...

  3. JAVA WEB接口开发简述

    目录 1. JAVA WEB接口开发简述 1.1. 基本了解 1.2. 提供接口 1.3. 调用接口 1. JAVA WEB接口开发简述 1.1. 基本了解 当我们想去访问其他网站的接口时候,而又不想 ...

  4. 数据库可视化操纵软件有navicat和sqlyog

    数据库可视化操纵软件有navicat和sqlyog 数据库可视化操纵软件有navicat和sqlyog

  5. ajax 请求 get请求成功,post 404 not found

    今天在做express的一个ajax请求的时候发现get请求成功,post 就一直是 404 not found, 研究了半天 才发现是express 的请求方法做了限制,之前一直都是用的 app.g ...

  6. C#——await与async实现多线程异步编程

    曾经,我们也许用过Thread.在主线程运行的时候.新开还有一个新线程,来运行新方法. 今天看别人发给我的一段代码的时候发现了一个不认识的await,可是又感觉非常熟悉的样子,感觉是线程那块儿的东西, ...

  7. JAVA获取操作系统的信息

    列出全部信息: Properties prop = System.getProperties(); prop.list(System.out); 获取某个信息: String os = prop.ge ...

  8. Jenkins安装与使用

    一.Jenkins简介 Jenkins是基于Java开发的一种持续集成工具,用于监控持续重复的工作,功能包括: 1.持续的软件版本发布/测试项目. 2.监控外部调用执行的工作 二.下载与安装 下载地址 ...

  9. luogu1155 双栈排序

    题目大意 运用两个栈的push和pop操作使得一个序列单调递增且操作字典序最小.$n\leq 1000$. 题解 本题我们要尝试运用“瞪眼法”,也就是推样例.我们显然要数字尽可能地推入第一个栈.那么问 ...

  10. POJ1664 计数 DP

      题目传送门 http://poj.org/problem?id=1664 设$dp[i][j]$表示$i$个苹果放在$j$个盘子里的总数 $1.$ 当 苹果数 小于 盘子数 $(M < N) ...