【Cocos2dx】【一】lua c++ 绑定
代码是在C++层面调用的,肯定可以直接用代码调用。那是否代表Lua 里,调用一个函数,C里也是这么执行的?原因也没看懂。lau.h 中定义了要注册到 lua 中的 c 函数的原型返回值(int)表示此函数有几个返回值,lua 执行完些函数之后根据这个返回值就知道需要从栈中取几个返回值.参数 lua_State* L,c 函数内可以使用 CAPI 来操作栈,与 lua 交互tolua 也叫 tolu
Cocos2dx lua c++ bind
为啥一般游戏基本都用Lua来做脚本,因为Lua本身就是C写的程序。Lua的解释器代码直接放入你的C/C++项目里,调用相关接口就可以用了。
在cocos2d-x中lua与C++的交互,主要借助于第三方工具tolua++来实现。
该工具会将C++按照Lua支持的C API指定生成绑定代码,以便于Lua通过这些绑定代码更快捷的访问C++下的类及方法相关。
以下内容大部分来参考链接的自文章,会根据自己的想法做些修改。
非常感谢
参考链接
cocos C++与Lua的交互
一.Lua 与 C++ 交互
想要做Lua和C++的交互,肯定先从原理入手。
lua学习03:tolua的编译和使用:C/C++调用lua、lua调用C++、lua调用tolua、常用tolua和lua的API介绍
1).Lua虚拟机/解释器 Lua_State
一般脚本语言的底层都是由静态语言实现的。
Lua的解释器就是一个C程序/模块,不过要注意它的版本和工程的版本对不对应的上。
在cocos中,C/C++就是lua的宿主,虚拟机说白了就是要提供一个lua运行的环境,该环境下需要保存Lua脚本运行的内存空间,全局变量,库文件等, 该环境被称为Lua_State。
在Lua_State环境下,若实现Lua与C/C++的数据交互,我们需要有个容器来对数据进行传递,这个容器就是Lua虚拟栈。
2.)Lua虚拟栈
栈是先进后出的,在Lua的虚拟栈中,栈中数据通过索引值进行定位,索引值可为正数,也可为负数。
通俗的来说,1的永远表示栈底,-1的永远表示栈顶。
可以这么理解,队列中第一个下标index肯定是1,栈中是先进后出,所以栈底肯定是1,因为它先进。
3).C++ 访问 Lua
文章里的样例写的很好,但是我想做一些自己的修改。
具体如何将Lua加入到C/C++项目工程请自行百度。
test.lua
-- 文件命名为:test.lua
str = "Get Lua Data Sucess!!!"
function Add(num1, num2)
return num1 + num2
end
.cpp
#include <iostream>
#include <string.h>
extern "C" {
#include "lua.h" // 提供了Lua的基本函数,在lua.h中的函数均已"lua_"为前缀
#include "lualib.h" // 定义lua的标准库函数,比如table, io, math等
#include "lauxlib.h" // 提供了辅助库相关,以"luaL_"为前缀
}
void main(){
// 创建lua环境,并加载标准库
lua_State* pL = lua_open();
luaL_openlibs(pL);
// 加载lua文件,返回0表示成功
int code = luaL_loadfile(pL, "test.lua");
if (code != 0){
return;
}
// 执行lua文件,参数分别为,lua环境,输入参数个数,返回值个数
lua_call(pL, 0, 0);
// 重置栈顶索引,设置为0表示栈清空
lua_settop(pL, 0);
// ------------- 读取变量 -------------
//lua_getglobal 主要做了这么几件事: 将参数压入栈中,lua获取参数的值后再将返回的结果压入栈中
lua_getglobal(pL, "str");
// 判定栈顶值类型是否为string,返回1表示成功,0表示失败
int isStr = lua_isstring(pL, 1);
if (isStr == 1) {
// 获取栈顶值,并将lua值转换为C++类型
std::string str = lua_tostring(pL, 1);
std::cout << "str = " << str.c_str() << std::endl;
}
// ------------- 读取函数 -------------
lua_getglobal(pL, "Add");
// 将函数所需要的参数入栈
lua_pushnumber(pL, 1); // 压入第一个参数
lua_pushnumber(pL, 2); // 压入第二个参数
/*
lua_pcall与lua_call类似,均用于执行lua文件,其方法分别为:
void lua_call(lua_State *L, int nargs, int nresults);
int lua_pcall(lua_State *L, int nargs, int nresults, int errfunc);
两者的区别在于:
前者在出现错误,程序会崩溃。后者多了一个errfunc索引,用于准确定位错误处理函数。
函数执行成功返回0,失败后可通过获取栈顶信息获取错误数据
两者的共同之处在于:
会根据nargs将参数按次序入栈,并根据nresults将返回值按次序填入栈中
若返回值结果数目大于nresults时,多余的将被丢弃;若小于nresults时,则按照nil补齐。
*/
int result = lua_pcall(pL, 2, 1, 0);
if (result != 0) {
const char *pErrorMsg = lua_tostring(pL, -1);
std::cout << "ERROR:" << pErrorMsg << std::endl;
lua_close(pL);
return;
}
/*
此处的栈中情况:
------------- 栈顶 -------------
正索引 负索引 类型 返回值
2 -1 number 3
1 -2 string "Get Lua Data Sucess!!!"
------------- 栈底 -------------
因此如下的索引获取数字索引可以使用-1或者2
*/
int isNum = lua_isnumber(pL, -1);
if (isNum == 1) {
double num = lua_tonumber(pL, -1);
std::cout << "num = " << num << std::endl;
}
// 关闭state环境,即销毁Lua_State对象,并释放Lua动态分配的空间
lua_close(pL);
system("pause");
}
1.添加C环境
Lua其实是C程序,再导入Lua的lib后需要这样引用
extern "C" {
#include "lua.h" // 提供了Lua的基本函数,在lua.h中的函数均已"lua_"为前缀
#include "lualib.h" // 定义lua的标准库函数,比如table, io, math等
#include "lauxlib.h" // 提供了辅助库相关,以"luaL_"为前缀
}
2.创建Lua虚拟机
必须有一个lua_State,才能执行lua代码
// 创建lua环境
lua_State* pL = lua_open();
//加载标准库
luaL_openlibs(pL);
3.加载lua文件 —> 执行lua代码
// 加载lua文件,返回0表示成功
int code = luaL_loadfile(pL, "test.lua");
if (code != 0){
return;
}
// 执行lua文件,参数分别为,lua环境,输入参数个数,返回值个数
lua_call(pL, 0, 0);
4.读取全局变量
// ------------- 读取变量 -------------
//lua_getglobal 主要做了这么几件事: 将参数压入栈中,lua获取参数的值后再将返回的结果压入栈中
lua_getglobal(pL, "str");
// 判定栈顶值类型是否为string,返回1表示成功,0表示失败
int isStr = lua_isstring(pL, 1);
if (isStr == 1) {
// 获取栈顶值,并将lua值转换为C++类型
std::string str = lua_tostring(pL, 1);
std::cout << "str = " << str.c_str() << std::endl;
}
5.使用lua函数
// ------------- 读取函数 -------------
lua_getglobal(pL, "Add");
// 将函数所需要的参数入栈
lua_pushnumber(pL, 1); // 压入第一个参数
lua_pushnumber(pL, 2); // 压入第二个参数
/*
lua_pcall与lua_call类似,均用于执行lua文件,其方法分别为:
void lua_call(lua_State *L, int nargs, int nresults);
int lua_pcall(lua_State *L, int nargs, int nresults, int errfunc);
两者的区别在于:
*前者在出现错误,程序会崩溃。后者多了一个errfunc索引,用于准确定位错误处理函数。
*函数执行成功返回0,失败后可通过获取栈顶信息获取错误数据
两者的共同之处在于:
*会根据nargs将参数按次序入栈,并根据nresults将返回值按次序填入栈中
*若返回值结果数目大于nresults时,多余的将被丢弃;若小于nresults时,则按照nil补齐。
*/
int result = lua_pcall(pL, 2, 1, 0);
if (result != 0) {
const char *pErrorMsg = lua_tostring(pL, -1);
std::cout << "ERROR:" << pErrorMsg << std::endl;
lua_close(pL);
return;
}
/*
此处的栈中情况:
------------- 栈顶 -------------
正索引 负索引 类型 返回值
2 -1 number 3
1 -2 string "Get Lua Data Sucess!!!"(上面获取全局变量"str"的返回值,一直在栈里没清除)
------------- 栈底 -------------
因此如下的索引获取数字索引可以使用-1或者2
*/
int isNum = lua_isnumber(pL, -1);
if (isNum == 1) {
double num = lua_tonumber(pL, -1);
std::cout << "num = " << num << std::endl;
}
按照代码来看,调用函数的时候。
-
lua_getglobal(pL, “Add”);
读取全局变量,压入栈中 -
lua_pushnumber(pL, 1);
压入参数 -
int result = lua_pcall(pL, 2, 1, 0);
pcall 执行lua函数,这时候Add是在栈底的,上面是参数。得到返回值,判断是否执行成功,pcall在执行出现error的时候会返回错误信息 -
lua_isnumber(pL, -1);
判断栈顶数据是否为需要的number类型数据。
从代码注释里可以看到,其实pcall之后,会把函数和参数数据从栈里面移除。所以最后栈里只剩下两个数据。
6.关闭Lua_State
// 关闭state环境,即销毁Lua_State对象,并释放Lua动态分配的空间
lua_close(pL);
7.总结
下图来自文章
有点没看懂。
- 代码是在C++层面调用的,肯定可以直接用代码调用。
- 那是否代表Lua 里,调用一个函数,C里也是这么执行的?
原因也没看懂。
4).Lua 访问 C/C++
lua入门教程:第八章 C API 注册c函数
lua入门教程:第九章 C API 另一种注册方式
lua入门教程:第十章 C API 类方式注册
1.流程
lua 调用 c 函数与 c 调用 lua 函数一样,都是通过 CAPI 和一个栈来实现。不同的地方在于,一般会写一个中间函数,来链接Lua和C++,并且把它注册到Lua内。
简单的逻辑可以直接使用中间函数写逻辑,但是复杂的逻辑中间函数就只能当粘合剂了。
C++ 编写 C++ 中间函数 —> C++ 注册中间函到Lua
Lua调用注册好的变量 —> C++ 中间函数 —> C++ 代码 —> 返回结果 —> C++ 中间函数把结果压入栈中 —> Lua 获取到结果
lua 调用 c 函数有以下几个步骤:
1. 在 c 代码中定义函数
lau.h 中定义了要注册到 lua 中的 c 函数的原型
int FunctionName(lua_State* L)
- 返回值(int)表示此函数有几个返回值,lua 执行完些函数之后根据这个返回值就知道需要从栈中取几个返回值.
- 参数 lua_State* L,c 函数内可以使用 CAPI 来操作栈,与 lua 交互
2.c 函数的实现流程:
- 从栈中取函数的参数。lua 调用 c 函数之前会把参数压入栈中,每个 c 函数都有一个独立的私有栈,所以函数第一个参数在栈中的索引是 1。
- 实现函数功能。
- 把返回值逐个压入栈中,第一个返回值最先压入,也就是在栈底;把结果压入栈之前不用手动清除栈中原来的参数和函数,lua 会自动帮我们清除。
- 返回一个整数表示该函数有几个返回值。
3. 把 c 函数注册到 lua 环境,在 lua 中创建一个全局变量保存这个函数
使用 lua_pushcfunction(L, c_fun_name) 把函数压入栈中
使用 lua_setglobal(L, l_fun_name) 把函数赋给一个全局变量
5).Lua 访问 C/C++ 之 Lua require C模块
这部分内容因为个人暂时用不到,所以不做详细解释,只有简单介绍。
如果想要在使用lua 解释器来运行 lua 代码时能够让 lua 调用 c 函数,那就得事先将 c 函数导出来。
require("youLib")
lua 的 require 是可以导入 .all 和 .so 的。把 youLib 弄成动态链接库即可。
具体的细节和注意事项(还不少),请继续看上面列出的这篇博客:Lua 基础之 Lua 程序,或者自行查询。
5).Lua 访问 C/C++ 之 C 访问 C模块
也可以在 c 程序中打开一个 lua 环境,然后加载上面的 c 模块到 lua 环境,最后执行 lua 代码块,让 lua 代码调用 c 函数。
使用 lua_requiref 在 c 程序中加载 c 模块完成函数注册,函数原型如下
luaL_requiref(lua_State, "模块名", 模块外部接口, 1);
6).Lua 访问 C/C++ 4) 5) 点结论
其实还是注册。。。
不同的概念是,相当于给自己的写的模块,增加了一个Lua使用的功能。只要别人用你的库,然后调用以下API,那它的lua代码就可以用这个模块的功能了。
二.Lua C API
这里就当字典,不知道干啥用的函数再来查。
字典补充:
Lua与C API交互全面解析(史上最全的整理)
lua中的lua_rawgeti和lua_rawseti函数用法
以下也来自文章,但做了些删改
// 获取栈顶索引即栈中元素的个数,因为栈底为1,所以栈顶索引为多少,就代表有多少个元素
// cocos 里经常用到,根据返回值数量做判断
int lua_gettop(lua_State *L);
/*
将栈顶索引设置为指定的数值
若设置的index比原栈顶高,则以nil补足。
若index比原栈顶低,高出的部分舍弃。
* 那看来就是只针对栈顶,不论正负数,只要超过栈顶就这样。
* 高了补nil,低了扔掉。
比如: 栈中有8个元素,若index为7,则表示删除了一个栈顶的元素。若index为0,表示清空栈
注意,index可为正数也可为负数,但若index为正数表示相对于栈底设置的,若为负数则相对于栈顶而设置的
* 直接清空栈
lua_settop(lua_State *L, 0);
*/
void lua_settop(lua_State *L, int index);
/*
将栈中索引元素的"副本"压入栈顶
比如:从栈底到栈顶,元素状态为10,20,30,40;若索引为3则元素状态为:10,20,30,40,30
* 就是复制 index 数据到栈顶,copy+push
类似的还有:
lua_pushnil: 压入一个nil值
lua_pushboolean: 压入一个bool值
lua_pushnumber: 压入一个number值
* 这不就是入栈操作?
*/
void lua_pushvalue(lua_State *L, int index);
/*
删除指定索引元素,并将该索引之上的元素填补空缺
比如:从栈底到栈顶,元素状态为10,20,30,40;若索引为-3则元素状态为10,30,40
* 删除指定位置栈数据
*/
void lua_remove(lua_State *L, int index);
/*
将栈顶元素替换索引位置的的元素
比如:从栈底到栈顶,元素状态为10,20,30,40,50;若索引为2则,元素状态为10,50,30,40
即索引为2的元素20被栈顶元素50替换
* 栈顶元素删除,并用指定位置的栈内元素替换栈顶元素
*/
void lua_replace(lua_State *L, int index);
/*
获取栈中指定索引元素的类型,若失败返回类型LUA_TNONE
其它类型有:
LUA_TBOOLEAN,
LUA_TNUMBER,
LUA_TSTRING,
LUA_TTABLE
LUA_TFUNCTION,
LUA_USERDATA等
*/
int lua_type(lua_State *L, int idx);
/*
检测栈中元素是否为某个类型,成功返回1,失败返回0
类似的还有:
lua_isnumber,
lua_isstring,
lua_iscfunction,
lua_isuserdata
* 站内的元素类型检查。话说不能外部传入检测吗?
*/
int lua_isXXX(lua_State *L, int index);
/*
将栈中元素转换为C语言指定类型。也只能转换栈内的,不能外部传入
*/
// lua_number
lua_Number lua_tonumber(lua_State *L, int idx);
// lua_int
lua_Integer lua_tointeger(lua_State *L, int idx);
// bool,实际上返回的int
int lua_toboolean(lua_State *L, int idx);
// char* string
const char* lua_tolstring(lua_State *L, int idx, size_t *len);
// lua_CFunction
lua_CFunction lua_tocfunction(lua_State *L, int idx);
// void* 无传出???
void* lua_touserdata(lua_State *L, int idx);
三.tolua
lua学习03:tolua的编译和使用:C/C++调用lua、lua调用C++、lua调用tolua、常用tolua和lua的API介绍
Lua 基础之 Lua 程序
【lua】使用 tolua 实现 lua 与 c++ 交互
以下大部分内容来自文章
1).tolua 简介
tolua 也叫 tolua++,是一个第三方库,简化了 lua 访问 c/c++ 的工作。tolua 自动导出 c/c++ 中的常量、变量、函数、类等,提供给 lua 直接访问和调用。
2).tolua 获取
tolua 的编译使用 cmake 来编译
编译完成之后,共有下面几个文件
- tolua.exe 用于从 .pkg 文件生成 .c/.cpp 文件
- tolua.dll 动态链接库文件
- tolua.lib 动态链接库对应的 lib 文件,如果是使用静态链接库项目生成的 lib 文件,则只有一个 lib 文件,没有 dll 文件
- tolua.h 头文件,在源代码的 include 目录下
由于 tolua 生成的 C/C++ 文件中,使用了很多 tolua 自己定义的方法。
所以不仅仅是用tolua工具生成 C/C++ 代码就ok的,还需
3).tolua 使用
-
第一步,正常编写 c/c++ 代码,编写对应的 package 文件(.pkg),package 文件语法基本与 .h 文件一样。注意下面几点即可:
- 不能有 public,private 等作用域修饰符,只能导出共有的成员,默认就是 public
- 函数原型和头文件保持一致,包括函数名参数和返回值
- 虚函数不能导出
- 在文件头使用 $#include “test.h” 包含头文件
-
使用 tolua.exe 从 package 文件生成相应的 c/c++ 源文件。
在 tolua.exe 目录打开命令行,输入tolua -n [mylib] -o [test.cpp] test.pkg。
- -n 参数指明这个文件所在的包,如果不写的话就默认跟文件名一样
- -o 指明生成的源文件名,这个参数是必须的
- 最后是源 package 文件。
生成的 tolua.exe 是依赖于动态链接库的,要把 tolua.dll 放在 tolua.exe 目录下。如果是使用静态链接库生成的 tolua.exe 则不用。
为了方便起见,也可以将 tolua.exe 添加在环境变量,把 tolua.exe 所在的路径添加到 path 变量中去即可。
-
在项目中使用 tolua 生成的文件,这里的文件包括
- 使用 c/c++ 写的原始代码
- 使用 tolua 导出的 c/c++ 源文件
导出的源文件中有一个方法 tolua_mylib_open,其中 mylib 就是上面 -n 参数指定的包名。
使用之前要先在 c++ 层创建 lua 环境之后调用 tolua_mylib_open 函数,之后 lua 代码就可以访问 c++ 的内容了,访问的时候是通过 tolua 调用到上面导出的源文件,然后再调用到原始的实现文件。
//C++ 注册 C++ -> LuaState -> tolua_mylib_open //Lua 使用 Lua -> LuaStack -> tolua -> 源文件 -> C/C++ 原始代码
4).详细过程以及注意事项,请看博客或自行查询
具体编写pkg文件以及其他注意,请详细看这篇文章
【lua】使用 tolua 实现 lua 与 c++ 交互
5).tolua_pushusertype C++对象操作
函数作用
-
传入 c++ 对象的 tolua++ 函数是 tolua_pushusertype。一般情况下,第一次使用这个函数将一个 c++ 对象 push 到 lua 堆栈上时,才会新建 userdata。tolua++ 会以 c++ 对象地址为键,userdata 为值,将键值对存储在 tolua_ubox 表里。下次推入同样的 c++ 对象时,从这个表里取出 userdata 推入堆栈即可
-
将c++对象指针作为key, 类型作为value存储于tolua_ubox表(当然这个表里还有userdata)。若key不存在直接插入即可;若存在则根据继承关系判断是否满足条件,若满足则不做任何处理直接返回此userdata对象,若不满足则修订value类型。
目的
构造一个给lua用的userdata对象
使用举例
//函数定义
void tolua_pushsertype(lua_State* L,void* value , const char* type)
//pRole是一个CRole的指针,指向一个CRole的对象
tolua_pushusertype(pScript->get_lua_state(),pRole,"CRole");
四.cocos Lua 框架
-
auto: 使用tolua++工具自动生成的C++代码相关
-
manual:放置了cocos扩展的一些功能,比如LuaEngine, LuaStack, LuaBridge(android, ios sdk交互相关)等
也就是说这里的交互代码无法用tolua生成了,只能自己手写了
-
luajit: 高效版的lua库,额外添加了lua没有的cocos库,并在对浮点计算,循环等进行了优化
luajit对lua版本有要求,好像是5.1以上的就用不了了
-
luasocket: 网络库相关
一般用用,要性能还是从C++自己写个类吧
-
tolua: tolua++库相关,实质是对Lua C库进行的再封装
-
xxtea: 加密相关
cocos引擎主要通过LuaEngine和LuaStack对Lua进行管理
- LuaEngine是一个管理LuaStack的单例
- LuaStack则用于对Lua_State进行了封装
Luastack可以多个??
1).Cocos lua 启动流程
以cocos2d-lua在启动时调用main.lua为例,简单的说主要分为三个步骤:
- 初始化LuaEngine,获取LuaState环境
- 注册C++模块相关到Lua中
- 执行Lua脚本
bool AppDelegate::applicationDidFinishLaunching()
{
// 初始化LuaEngine,在getInstance中会初始化LuaStack,LuaStack初始化Lua环境相关
auto engine = LuaEngine::getInstance();
// 将LuaEngine添加到脚本引擎管理器ScriptEngineManager中
ScriptEngineManager::getInstance()->setScriptEngine(engine);
// 获取Lua环境
lua_State* L = engine->getLuaStack()->getLuaState();
// 注册额外的C++ API相关,比如cocosstudio, spine, audio相关
lua_module_register(L);
// 这个得再了解了解
register_all_packages();
// 设置cocos自带的加密相关
// 在LuaStack::executeScriptFile执行脚本文件时,会通过LuaStack::luaLoadBuffer对文件进行解密
LuaStack* stack = engine->getLuaStack();
stack->setXXTEAKeyAndSign("2dxLua", strlen("2dxLua"), "XXTEA", strlen("XXTEA"));
// 执行Lua脚本文件
if (engine->executeScriptFile("main.lua"))
{
return false;
}
return true;
}
LuaStack::init()
extern "C" {
#include "lua.h"
#include "tolua++.h"
#include "lualib.h"
#include "lauxlib.h"
}
bool LuaStack::init(void)
{
// 初始化Lua环境并打开标准库
_state = lua_open();
luaL_openlibs(_state);
// 可以看到之前没有使用tolua,只有上面两步。
// 看了下源码,创建了几个table
toluafix_open(_state);
// 注册全局函数print到lua中,它会覆盖lua库中的print方法
const luaL_reg global_functions [] = {
{"print", lua_print},
{"release_print",lua_release_print},
{nullptr, nullptr}
};
// 注册全局变量
luaL_register(_state, "_G", global_functions);
// 注册cocos2d-x引擎的API到lua环境中
g_luaType.clear();
register_all_cocos2dx(_state);
...
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC)
// 导入ios下调用object-c相关API
LuaObjcBridge::luaopen_luaoc(_state);
#endif
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
// 导入android下调用java相关API
LuaJavaBridge::luaopen_luaj(_state);
#endif
// 添加Lua的加载器,该方法将cocos2dx_lua_loader方法添加到Lua全局变量package下的loaders成员中
// 当requires加载脚本时,Lua会使用package下的loaders中的加载器,即cocos2dx_lua_loader来加载
// 设定cocos2dx_lua_loader,可以使得我们自定义设置搜索路径相关,且拓展实现对脚本的加密解密相关
addLuaLoader(cocos2dx_lua_loader);
return true;
}
1.toluafix_open
TOLUA_API void toluafix_open(lua_State* L)
{
lua_pushstring(L, TOLUA_REFID_PTR_MAPPING);
lua_newtable(L);
lua_rawset(L, LUA_REGISTRYINDEX);
lua_pushstring(L, TOLUA_REFID_TYPE_MAPPING);
lua_newtable(L);
lua_rawset(L, LUA_REGISTRYINDEX);
lua_pushstring(L, TOLUA_REFID_FUNCTION_MAPPING);
lua_newtable(L);
lua_rawset(L, LUA_REGISTRYINDEX);
}
看了下源码是创建了这几个table
2.addLuaLoader
- 添加Lua的加载器,该方法将cocos2dx_lua_loader方法添加到Lua全局变量package下的loaders成员中
- 当requires加载脚本时,Lua会使用package下的loaders中的加载器,即cocos2dx_lua_loader来加载
- 设定cocos2dx_lua_loader,可以使得我们自定义设置搜索路径相关,且拓展实现对脚本的加密解密相关
void LuaStack::addLuaLoader(lua_CFunction func)
void LuaStack::addLuaLoader(lua_CFunction func)
{
if (!func)
return;
// 1.看来不同版本的名字还不一样
#if LUA_VERSION_NUM >= 504 || (LUA_VERSION_NUM >= 502 && !defined(LUA_COMPAT_LOADERS))
const char* realname = "searchers";
#else
const char* realname = "loaders";
#endif
// stack content after the invoking of the function
// get loader table
// 2.获取全局变量package,把package压入栈
lua_getglobal(_state, "package");
/* L: package */
// 3.lua_getfield 将t[k]压入堆栈,t由参数index指定在栈中的位置;
// package[realname] 压入栈顶
lua_getfield(_state, -1, realname);
/* L: package, loaders */
// insert loader into index 2
// 4.把 cocos2dx loader 压入栈顶
lua_pushcfunction(_state, func);
/* L: package, loaders, func */
// lua_objlen 获取 loaders 大小
// 把loader 里,从 2-len的 loader func 全部往后移1位
for (int i = (int)(lua_objlen(_state, -2) + 1); i > 2; --i)
{
lua_rawgeti(_state, -2, i - 1);
/* L: package, loaders, func, function */
// we call lua_rawgeti, so the loader table now is at -3
lua_rawseti(_state, -3, i);
/* L: package, loaders, func */
}
//loaders[2] = func
lua_rawseti(_state, -2, 2);
/* L: package, loaders */
// set loaders into package
//package[loaders] = loaders(从栈里取出来的)
lua_setfield(_state, -2, realname);
/* L: package */
lua_pop(_state, 1);
}
3.cocod2dx_lua_loader
Cocos2dxLuaLoader.cpp
#include "scripting/lua-bindings/manual/Cocos2dxLuaLoader.h"
#include <string>
#include <algorithm>
#include "scripting/lua-bindings/manual/CCLuaStack.h"
#include "scripting/lua-bindings/manual/CCLuaEngine.h"
#include "platform/CCFileUtils.h"
using namespace cocos2d;
extern "C"
{
int cocos2dx_lua_loader(lua_State *L)
{
// lua 或者 luac 文件
static const std::string BYTECODE_FILE_EXT = ".luac";
static const std::string NOT_BYTECODE_FILE_EXT = ".lua";
// require传入的要加载的文件名,比如:require "cocos.init" 下的"cocos.init"
std::string filename(luaL_checkstring(L, 1));
// 去掉后缀名".luac"或“.lua”
size_t pos = filename.rfind(BYTECODE_FILE_EXT);
if (pos != std::string::npos && pos == filename.length() - BYTECODE_FILE_EXT.length())
filename = filename.substr(0, pos);
else
{
pos = filename.rfind(NOT_BYTECODE_FILE_EXT);
if (pos != std::string::npos && pos == filename.length() - NOT_BYTECODE_FILE_EXT.length())
filename = filename.substr(0, pos);
}
// 将"."替换为"/"
// "cocos.init" ---> "cocos/init"
pos = filename.find_first_of('.');
while (pos != std::string::npos)
{
filename.replace(pos, 1, "/");
pos = filename.find_first_of('.');
}
// search file in package.path
Data chunk;
std::string chunkName;
FileUtils* utils = FileUtils::getInstance();
// 获取package.path的变量
lua_getglobal(L, "package");
lua_getfield(L, -1, "path");
// 通过package.path获取搜索路径相关,该路径为模版路径,格式类似于:
// ?; ?.lua; c:\windows\?; /usr/local/lua/lua/?/?.lua 以“;”作为分割符
std::string searchpath(lua_tostring(L, -1));
//除栈底数据全出栈
lua_pop(L, 1);
size_t begin = 0;
size_t next = searchpath.find_first_of(';', 0);
// 遍历package.path中的所有路径,查找文件是否存在,若文件存在则通过getDataFromFile读取文件数据
do
{
if (next == std::string::npos)
next = searchpath.length();
std::string prefix = searchpath.substr(begin, next-begin);
if (prefix[0] == '.' && prefix[1] == '/')
prefix = prefix.substr(2);
pos = prefix.rfind(BYTECODE_FILE_EXT);
if (pos != std::string::npos && pos == prefix.length() - BYTECODE_FILE_EXT.length())
{
prefix = prefix.substr(0, pos);
}
else
{
pos = prefix.rfind(NOT_BYTECODE_FILE_EXT);
if (pos != std::string::npos && pos == prefix.length() - NOT_BYTECODE_FILE_EXT.length())
prefix = prefix.substr(0, pos);
}
// 将?替换为文件名,获取搜索路径名,比如:?.lua替换为cocos/init.lua
pos = prefix.find_first_of('?', 0);
while (pos != std::string::npos)
{
prefix.replace(pos, 1, filename);
pos = prefix.find_first_of('?', pos + filename.length() + 1);
}
chunkName = prefix + BYTECODE_FILE_EXT;
if (utils->isFileExist(chunkName)) // && !utils->isDirectoryExist(chunkName))
{
chunk = utils->getDataFromFile(chunkName);
break;
}
else
{
chunkName = prefix + NOT_BYTECODE_FILE_EXT;
if (utils->isFileExist(chunkName) ) //&& !utils->isDirectoryExist(chunkName))
{
chunk = utils->getDataFromFile(chunkName);
break;
}
else
{
chunkName = prefix;
if (utils->isFileExist(chunkName)) // && !utils->isDirectoryExist(chunkName))
{
chunk = utils->getDataFromFile(chunkName);
break;
}
}
}
// 指定搜素路径下不存在该文件,则下一个
begin = next + 1;
next = searchpath.find_first_of(';', begin);
} while (begin < searchpath.length());
// 判定文件内容是否获取成功
if (chunk.getSize() > 0)
{
LuaStack* stack = LuaEngine::getInstance()->getLuaStack();
stack->luaLoadBuffer(L, reinterpret_cast<const char*>(chunk.getBytes()),
static_cast<int>(chunk.getSize()), chunkName.c_str());
}
else
{
CCLOG("can not get file data of %s", chunkName.c_str());
return 0;
}
return 1;
}
}
LuaStack.cpp
int LuaStack::luaLoadBuffer(lua_State *L, const char *chunk, int chunkSize, const char *chunkName)
{
int r = 0;
// 判定是否加密,若lua脚本加密,则解密后在加载脚本文件
// luaL_loadbuffer 用于“加载并编译”Lua代码,并将其压入栈中
if (_xxteaEnabled && strncmp(chunk, _xxteaSign, _xxteaSignLen) == 0)
{
// decrypt XXTEA
xxtea_long len = 0;
unsigned char* result = xxtea_decrypt((unsigned char*)chunk + _xxteaSignLen,
(xxtea_long)chunkSize - _xxteaSignLen,
(unsigned char*)_xxteaKey,
(xxtea_long)_xxteaKeyLen,
&len);
unsigned char* content = result;
xxtea_long contentSize = len;
skipBOM((const char*&)content, (int&)contentSize);
r = luaL_loadbuffer(L, (char*)content, contentSize, chunkName);
free(result);
}
else
{
skipBOM(chunk, chunkSize);
r = luaL_loadbuffer(L, chunk, chunkSize, chunkName);
}
// 判定内容是否存在错误
#if defined(COCOS2D_DEBUG) && COCOS2D_DEBUG > 0
if (r)
{
switch (r)
{
// 语法错误
case LUA_ERRSYNTAX:
CCLOG("[LUA ERROR] load \"%s\", error: syntax error during pre-compilation.", chunkName);
break;
// 内存分配错误
case LUA_ERRMEM:
CCLOG("[LUA ERROR] load \"%s\", error: memory allocation error.", chunkName);
break;
// 文件错误
case LUA_ERRFILE:
CCLOG("[LUA ERROR] load \"%s\", error: cannot open/read file.", chunkName);
break;
// 未知错误
default:
CCLOG("[LUA ERROR] load \"%s\", error: unknown.", chunkName);
}
}
#endif
return r;
}
4.结论
- lua脚本的加载,通过cocos2d-x自定义的cocos2dx_lua_loader实现。
- Lua脚本的加密解密相关,及对lua脚本内容检测错误相关,通过luaLoadBuffer来实现。
2).Cocos lua tolua 使用
这个网上的教程就非常多了
【cocos2d-x】使用 tolua 导出自定义 c++ 类
[Cocos2dx]C++自定义类绑定到Lua
以下大部分内容来自上面两篇博客
1.准备环境
主要参考 tools\tolua\README.md
两个环境变量,NDK_ROOT(有改动,看下面) 和 PYTHON_BIN。
NDK 的需求版本 和 python 的版本 README.md 里有。
a.建议使用 pip 装,不用自己去下
Windows10下python3和python2同时安装(二)python2.exe、python3.exe和pip2、pip3设置
b.pyCheetah 现在没有了
// 用这个
pip install Cheetah
c.NDK_ROOT 改用 ANDROID_NDK
genbindings.py
def _check_ndk_root_env():
''' Checking the environment ANDROID_NDK, which will be used for building
'''
try:
ANDROID_NDK = None
sdkRoot = os.environ.get('ANDROID_HOME', None)
for _, ndkVers, _ in os.walk("{0}{1}ndk".format(sdkRoot, os.path.sep)):
for ndkVer in ndkVers:
if (ndkVer == '19.2.5345600'):
ANDROID_NDK = "{0}{1}ndk{1}{2}".format(sdkRoot, os.path.sep, ndkVer)
break
break
if ANDROID_NDK == None:
ANDROID_NDK = os.environ.get('ANDROID_NDK', None)
except:
print('Exception occurred when check_ndk_root_env!')
return ANDROID_NDK
2.bindings-generator
官方用Python写的一个工具 bindings-generator
如果只使用tolua,那我们需要自己编写很多的 .pkg 文件。但是目前Cocos2dx不这么做了,使用 bindings-generator脚本来个做这方面的处理。
bindings-generator脚本的工作机制
- 不用编写.pkg和.h文件了,直接定义一个ini文件,注册到Lua环境里的模块名是什么,就行了。
- 摸清了toLua++工具的生成方法,改由Python脚本动态分析C++类,自动生成桥接的.h和.cpp代码,不调用tolua++命令了
- 虽然不再调用tolua++命令了,但是底层仍然使用toLua++的库函数,比如tolua_function,bindings-generator脚本生成的代码就跟使用toLua++工具生成的几乎一样。
所以会发现 tolua 下都是 .ini 文件,而没有 .pkg。
所以接下来的就是按 bindings-generator 的流程走了
3.在 framework/cocos2d-x/tools/tolua,新建一个 .ini 配置文件
[cocos2dx_binding] # 标记修改
# the prefix to be added to the generated functions. You might or might not use this in your own
# templates
prefix = cocos2dx_binding # 标记修改
# create a target namespace (in javascript, this would create some code like the equiv. to `ns = ns || {}`)
# all classes will be embedded in that namespace
target_namespace = cc # 标记修改
# --------- ?
#android_headers = -I%(androidndkdir)s/platforms/android-19/arch-arm/usr/include -I%(androidndkdir)s/sources/cxx-stl/gnu-libstdc++/4.7/libs/armeabi-v7a/include -I%(androidndkdir)s/sources/cxx-stl/gnu-libstdc++/4.7/include -I%(androidndkdir)s/sources/cxx-stl/gnu-libstdc++/4.8/libs/armeabi-v7a/include -I%(androidndkdir)s/sources/cxx-stl/gnu-libstdc++/4.8/include
android_headers = -I%(androidndkdir)s/platforms/android-19/arch-arm/usr/lib -I%(androidndkdir)s/sources/cxx-stl/gnu-libstdc++/4.9/libs/armeabi-v7a/include -I%(androidndkdir)s/sources/cxx-stl/gnu-libstdc++/4.9/include -I%(androidndkdir)s/sources/cxx-stl/gnu-libstdc++/4.9/libs/armeabi-v7a/include -I%(androidndkdir)s/sources/cxx-stl/gnu-libstdc++/4.9/include
# --------- ?
android_flags = -D_SIZE_T_DEFINED_
#clang_headers = -I%(clangllvmdir)s/lib/clang/%(clang_version)s/include
clang_headers = -I%(clangllvmdir)s/lib64/clang/5.0/include
clang_flags = -nostdinc -x c++ -std=c++11 -U __SSE__
# --------- ?
# -I%(cocosdir)s/jsext -I%(cocosdir)s/jsext/system -I%(cocosdir)s/jsext/alipay -I%(cocosdir)s/jsext/video -I%(cocosdir)s/jsext/webview -I%(cocosdir)s/jsext/umeng
cocos_headers = -I%(cocosdir)s -I%(cocosdir)s/cocos -I%(cocosdir)s/cocos/base -I%(cocosdir)s/cocos/platform/android -I%(cocosdir)s/extensions -I%(cocosdir)s/external -I%(cocosdir)s/cocos/editor-support -I%(cocosdir)s/cocos/network -I%(cocosdir)s/cocos/ui/UIEditBox -I%(cocosdir)s/cocos/ui
#cocos_headers = -I%(cocosdir)s -I%(cocosdir)s/cocos -I%(cocosdir)s/cocos/platform/android
cocos_flags = -DANDROID
cxxgenerator_headers =
# extra arguments for clang
extra_arguments = %(android_headers)s %(clang_headers)s %(cxxgenerator_headers)s %(cocos_headers)s %(android_flags)s %(clang_flags)s %(cocos_flags)s %(extra_flags)s
# what headers to parse 头文件路径
headers = %(cocosdir)s/cocos/mybinding/MyBinding.h # 标记修改
# what classes to produce code for. You can use regular expressions here. When testing the regular
# expression, it will be enclosed in "^$", like this: "^Menu*$".
#包含的类,新添加文件需要修改
classes = MyBinding.* # 标记修改
#需要在js里面派生的类
#classes_need_extend = MyBinding # 标记修改
# what should we skip? in the format ClassName::[function function]
# ClassName is a regular expression, but will be used like this: "^ClassName$" functions are also
# regular expressions, they will not be surrounded by "^$". If you want to skip a whole class, just
# add a single "*" as functions. See bellow for several examples. A special class name is "*", which
# will apply to all class names. This is a convenience wildcard to be able to skip similar named
# functions from all classes.
skip =
rename_functions =
rename_classes =
# for all class names, should we remove something when registering in the target VM?
remove_prefix =
# classes for which there will be no "parent" lookup
classes_have_no_parents =
# base classes which will be skipped when their sub-classes found them.
base_classes_to_skip = Ref
# classes that create no constructor
# Set is special and we will use a hand-written constructor
abstract_classes =
# Determining whether to use script object(js object) to control the lifecycle of native(cpp) object or the other way around. Supported values are 'yes' or 'no'.
script_control_cpp = no
注意检查一下三个头文件对应的路径及文件问题
- android_headers
- clang_headers
- cocos_headers
.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:不在使用。
+ •classes_have_no_parents:要配置是过滤器所需要的父类。这个选项是很少修改。
+ •abstract_classes:要配置的公共构造并不需要导出的类。
+ •script_control_cpp:是的。要配置脚本层是否管理对象的生命周期。如果没有,那么C++层关心他们的生命周期。现在,它是不完善的,以控制原生对象的续航时间在脚本层。所以,你可以简单地把它设置为no
4.新增 framework/cocos2d-x/tools/tolua 里面的 genbindings.py
找到cmd_args,然后添加
'cocos2dx_youlib.ini' : ('cocos2dx_youlib', 'lua_cocos2dx_youlib_auto'), \
5.确保NDK_ROOT 和PYTHON_BIN安装切配置好了,运行 genbindings.py
6.CCLuaStack.cpp 里添加注册函数
确保已经在 auto里生成了 .h 和 .cpp
打开CCLuaStack.cpp,添加
#include "lua_cocos2dx_youlib_auto.hpp"
register_all_cocos2dx_youlib(_state);
7.在你的工程里把 youlib.hpp 和 youlib.cpp 加入进去
3).Cocos lua 功能接入
1.fairyGUI
4).问题归纳
1.no member name ‘xxx’ in namspace ‘std’
一开始自己也懵,这个东西又没有编译,怎么会有这种错误。
后来想想,在整个流程里参与生成代码,和 C++ 有关的似乎只有 ndk
确实是这个原因。个人使用的是社区版的cocos2dx,目前它们的 c++ 版本是 17/20,最低支持 c++ 17 的ndk 是 18b。
问了群里的大佬,就是用最新的,READEME.md给你的只是最低版本,大部分情况下直接用最新的,不用压着最低版本。
2.Cocos2d-x 3.5 Lua Binding Section not found in config) file
你的 .ini 配错了!
更多推荐
所有评论(0)