【Cocos2dx】【一】lua c++ 绑定

Cocos2dx lua c++ bind

Lua官方页面

为啥一般游戏基本都用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;
}

按照代码来看,调用函数的时候。

  1. lua_getglobal(pL, “Add”);
    读取全局变量,压入栈中

  2. lua_pushnumber(pL, 1);
    压入参数

  3. int result = lua_pcall(pL, 2, 1, 0);
    pcall 执行lua函数,这时候Add是在栈底的,上面是参数。得到返回值,判断是否执行成功,pcall在执行出现error的时候会返回错误信息

  4. lua_isnumber(pL, -1);
    判断栈顶数据是否为需要的number类型数据。

从代码注释里可以看到,其实pcall之后,会把函数和参数数据从栈里面移除。所以最后栈里只剩下两个数据。

6.关闭Lua_State
// 关闭state环境,即销毁Lua_State对象,并释放Lua动态分配的空间
lua_close(pL);
7.总结

下图来自文章
在这里插入图片描述
有点没看懂。

  1. 代码是在C++层面调用的,肯定可以直接用代码调用。
  2. 那是否代表Lua 里,调用一个函数,C里也是这么执行的?
    原因也没看懂。

4).Lua 访问 C/C++

lua入门教程:第八章 C API 注册c函数
lua入门教程:第九章 C API 另一种注册方式
lua入门教程:第十章 C API 类方式注册

Lua 基础之 Lua 程序

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 函数的实现流程:
  1. 从栈中取函数的参数。lua 调用 c 函数之前会把参数压入栈中,每个 c 函数都有一个独立的私有栈,所以函数第一个参数在栈中的索引是 1。
  2. 实现函数功能。
  3. 把返回值逐个压入栈中,第一个返回值最先压入,也就是在栈底;把结果压入栈之前不用手动清除栈中原来的参数和函数,lua 会自动帮我们清除。
  4. 返回一个整数表示该函数有几个返回值。
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 使用

  1. 第一步,正常编写 c/c++ 代码,编写对应的 package 文件(.pkg),package 文件语法基本与 .h 文件一样。注意下面几点即可:

    • 不能有 public,private 等作用域修饰符,只能导出共有的成员,默认就是 public
    • 函数原型和头文件保持一致,包括函数名参数和返回值
    • 虚函数不能导出
    • 在文件头使用 $#include “test.h” 包含头文件
  2. 使用 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 变量中去即可。

  1. 在项目中使用 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++对象操作

函数作用

  1. 传入 c++ 对象的 tolua++ 函数是 tolua_pushusertype。一般情况下,第一次使用这个函数将一个 c++ 对象 push 到 lua 堆栈上时,才会新建 userdata。tolua++ 会以 c++ 对象地址为键,userdata 为值,将键值对存储在 tolua_ubox 表里。下次推入同样的 c++ 对象时,从这个表里取出 userdata 推入堆栈即可

  2. 将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为例,简单的说主要分为三个步骤:

  1. 初始化LuaEngine,获取LuaState环境
  2. 注册C++模块相关到Lua中
  3. 执行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.结论
  1. lua脚本的加载,通过cocos2d-x自定义的cocos2dx_lua_loader实现。
  2. 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脚本的工作机制

  1. 不用编写.pkg和.h文件了,直接定义一个ini文件,注册到Lua环境里的模块名是什么,就行了。
  2. 摸清了toLua++工具的生成方法,改由Python脚本动态分析C++类,自动生成桥接的.h和.cpp代码,不调用tolua++命令了
  3. 虽然不再调用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
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值