皇冠新现金官网 > 皇冠官网 > tolua++技术分析 cocos2dx+lua,linux指令编译
tolua++技术分析 cocos2dx+lua,linux指令编译
2020-01-01 191

tolua++技术分析 cocos2dx+lua

VC要编译LUA文件必须先配置VC编程环境。。我用的是VC6.0,lua 5.1.4版

一、附上手册,供读者查阅

  • lua5.3官方手册:http://www.lua.org/manual/5.3/
  • lua5.3中文手册(偶像云风大神翻译):http://cloudwu.github.io/lua53doc/

前言

一直都使用 cocos2dx + lua 进行游戏开发,用 Lua 开发可以专注于游戏逻辑的实现,另外一方面可以实现热更新;而且 lua 是一个轻量级的脚本语言,库小但是功能齐全,所以在业内非常受欢迎。之前看了网上很多关于 c/c++ 如何与 lua 互调的讲解,也查看了 lua 官网的 lua api 和 c api,感觉大有收获。最近这一段时间研究了 tolua++ 里面 lua 层调用 c/c++ 的技术实现,准备记录一下学习心得,这样可以让自己对 tolua++ 工作机制理解的更加流畅,也希望自己对原理和 api 的分析能够对其他人有帮助!


 

二、预备知识

  • lua搜索路径: package.path, package.cpath
  • require规则: 举个例子,require “a.b” 或者require "a/b" 优先搜索目录package.path下的lua文件。假如未搜索到,那么搜索package.cpath中的.so文件,调用luaopen_a_b函数;
  • .so: 全称sharedobject(共享动态链接库),类似于windows下的dll文件,供lua调用;
  • luaL_len, luaL_newlib 为lua5.3版本api,lua_objlen、luaL_register为lua5.1版本api;

tolua++需要将 c/c++ 中的类型,变量,函数,对象导出到lua

  1. 通过 tolua_reg_types(lua_State* tolua_S) 将类型导出,作用是为每一个需要导出到 lua 中的 c++ 类型创建元表,比如 CCNode 这种类型,就会在注册表中创建一个元表 CCNode_mt。( 之后会用 _R 代表注册表 , _G 代表全局表 , type_mt 代表类型为type的元表。 )

  2. 通过 tolua_cclass (lua_State* L, const char* lname, const char* name, const char* base, lua_CFunction col) 把基类设为子类的元表;同时在 _R.tolua_super 中以子类的元表为键,创建一张表作为值,而这张表会以基类,基类的基类(递归下去)为键,true/false 为值 ; 还会让基类和子类共同享有同一张tolua_ubox表(以c++指针为键 , fulluserdata为值)。 最后让 _G 持有 name_mt,即:_G.lname = name_mt。所以对于一个 c++ 类型,tolua++ 为其创建的元表最终会让全局表和注册表共同持有。

  3. 通过 tolua_function (lua_State* L, const char* name, lua_CFunction func) 将成员方法导出到 步骤1 创建的元表中;即:_G.type_mt.name = func

  4. 通过 tolua_variable(lua_State* L, const char* name, lua_CFunction get, lua_CFunction set) 在c++类型对象的元表中准备两张表_G.type_mt.get = {} , _G.type_mt.set = {} ; 两张表以变量名为键,get/set方法为值。

首先将lua的"lua.h"  ,"lualib.h", "lauxlib.h" 这三个放在vc程序include文件夹下

三、代码如下

1、test.c附详细注释

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

static int lua_add(lua_State *L) {
    int a = lua_tonumber(L, 1); //lua_tonumber:取浮点数 lua_tointeger: 取整形数
    int b = lua_tonumber(L, 2);
    lua_pushinteger(L, a + b); //将和压栈,返回给lua
    return 1; //1表示一个返回值(a + b)
}

static int lua_table_incrbyone(lua_State *L) {
    int len = luaL_len(L, 1);
    int a[len];
    for (int i = 0; i < len; ++i) {
        /*
            -1表示栈顶;
            取栈顶table中索引为i+1位置的值并压栈;
            区别于下述lua_getfield,一个数组一个键值对;
            这里也可以这样写,lua_rawgeti(L, 1, i + 1),栈内只有一个元素时候-1(自上往下数)位置和1(自下往上数)位置元素相同;
        */
        lua_rawgeti(L, -1, i + 1); 
        a[i] = lua_tointeger(L, -1);
        lua_pop(L, 1); //弹出上述压栈元素,注意操作栈内元素大部分通过压栈,然后取出,最后通过pop还原栈状态
    }

    for (int i = 0; i < len; ++i) {
        //*方法一,易理解,压入键,压入值
        lua_pushinteger(L, i + 1);
        lua_pushinteger(L, a[i] + 1);
        lua_settable(L, -3);//将stack.top()元素作为值,-2位置元素作为键,放到-3位置的table中
        //*/

        /*方法二
        lua_pushinteger(L, a[i] + 1); //元素压入栈顶
        lua_rawseti(L, -2, i + 1); //放到-2位置表中,设置索引为i+1对应的值,并从栈顶弹出
        //*/
    }
    return 0;
}

static int lua_table_return_incrbyone(lua_State *L) {
    int len = luaL_len(L, 1);
    int a[len];
    for (int i = 0; i < len; ++i) {
        lua_rawgeti(L, 1, i + 1);
        a[i] = lua_tointeger(L, -1);
        lua_pop(L, 1);
    }

    lua_newtable(L); //返回新表的话要新建一个table
    for (int i = 0; i < len; ++i) {
        lua_pushinteger(L, i + 1);
        lua_pushinteger(L, a[i] + 1);
        lua_settable(L, -3);
    }
    return 1;
}

static int lua_table_kv(lua_State *L) {
    lua_getfield(L, -1, "name"); //取栈顶table中字段为"name"的值, 并压栈
    const char *name = lua_tostring(L, -1); //name拿到,原谅我没用到,只是演示操作
    lua_pop(L, 1); //将上述压入栈顶的元素弹出

    lua_getfield(L, -1, "age");
    int age = lua_tointeger(L, -1);
    lua_pop(L, 1);

    lua_getfield(L, -1, "score");
    int len = luaL_len(L, -1);
    int a[len];
    for (int i = 0; i < len; ++i) {
        lua_rawgeti(L, -1, i + 1);
        a[i] = lua_tointeger(L, -1);
        lua_pop(L, 1);
    }
    lua_pop(L, 1);

    lua_pushstring(L, "Red"); //”Red"压栈
    lua_setfield(L, -2, "name"); //设置-2位置"name"的值为栈顶元素“Red”,并将栈顶元素弹出

    lua_pushinteger(L, 17);
    lua_setfield(L, -2, "age");

    lua_newtable(L);
    for (int i = 0; i < len; ++i) {
        lua_pushinteger(L, i+1);
        lua_pushinteger(L, a[i]+1);
        lua_settable(L, -3);
    }
    lua_setfield(L, -2, "score");
    return 0;
}

//lua函数和c层函数建立映射关系
static const struct luaL_Reg funarr[] = {
    {"add", lua_add},
    {"table_incrbyone", lua_table_incrbyone},
    {"table_return_incrbyone", lua_table_return_incrbyone},
    {"table_kv", lua_table_kv},
    {NULL, NULL}, //这是个末尾哨兵,好像不加也没事。。。。
};

//编译后会创建一个lualib.so库,给lua调用
int luaopen_lualib(lua_State *L) {
    /*
    再多解释也莫过于luaL_newlib的官方说明,官方手册连接:https://www.lua.org/manual/5.3/
    void luaL_newlib (lua_State *L, const luaL_Reg l[]);
    Creates a new table and registers there the functions in list l.

    It is implemented as the following macro:

         (luaL_newlibtable(L,l), luaL_setfuncs(L,l,0))
    The array l must be the actual array, not a pointer to it.
    */
    luaL_newlib(L, funarr); 
    return 1;
}

2、test.lua

local lualib = require "lualib"

print("===========两数相加例子===========")
local sum = lualib.add(3, 4)
print("sum", sum)

print("nn===========传递表(对原来的表操作)例子===========")
local a = {1, 2, 3, 4, 5}
lualib.table_incrbyone(a)
for i = 1, #a do
    print("a["..i.."]", a[i])
end

print("nn===========传递表(表中数据操作后返回新表)例子===========")
local a = {1, 2, 3, 4, 5}
local b = lualib.table_return_incrbyone(a)
for i = 1, #b do
    print("b["..i.."]", b[i])
end

print("nn===========传递键值对例子===========")
local c = {
    name = "Blue",
    age = 18,
    score = {80, 90}
}
lualib.table_kv(c)
for k,v in pairs(c) do
    if type(v) ~= "table" then
        print("k", k, "v", v)
    else
        print("k", k)
        for kk, vv in pairs(v) do
            print("kk", kk, "vv", vv)
        end
    end
end

3、makefile文件

PLATS = linux macosx

none :
    @echo "Please do 'make PLATFORM' where PLATFORM is one of these:"
    @echo "$(PLATS) "

linux:
    gcc -std=c99 -fPIC -shared -o test.c
    lua test.lua

macosx:
    gcc -c test.c
    gcc -O2 -bundle -undefined dynamic_lookup -o lualib.so test.o
    rm -rf *.o
    lua test.lua

此处提供mac、linux编译方法,windows还没去研究哈哈哈,mac下用make macosx指令编译,linux下用make linux指令编译。只修改test.lua文件的话,只需lua test.lua运行lua文件,不需要重新编译;

4、运行输出

Blue:LuaCAPI Walker$ make macosx
gcc -c test.c
gcc -O2 -bundle -undefined dynamic_lookup -o lualib.so test.o
rm -rf *.o
lua test.lua
===========两数相加例子===========
sum 7


===========传递表(对原来的表操作)例子===========
a[1]    2
a[2]    3
a[3]    4
a[4]    5
a[5]    6


===========传递表(表中数据操作后返回新表)例子===========
b[1]    2
b[2]    3
b[3]    4
b[4]    5
b[5]    6


===========传递键值对例子===========
k   name    v   Red
k   score
kk  1   vv  81
kk  2   vv  91
k   age v   17

上面c++数据的导出步骤就是 tolua_Cocos2d_open(lua_State* tolua_S) 的实现,在CCLuaStack 初始化的时候完成。

 

tolua_Cocos2d_open (lua_State* tolua_S)内容分析

然后将lua的lua5.1.lib放在lib文件夹下就OK了

1: tolua_open(tolua_S) 创建一系列全局的table

  • _R.tolua_opened = true

  • _R.tolua_value_root = { }

  • _R.tolua_ubox = { }

  • setmetatable(_R.tolua_ubox,{__mode = v })

  • _R.tolua_super = { }

  • _R.tolua_gc = { }

  • _R.tolua_gc_event = class_gc_event(_R.tolua_gc , _R.tolua_super)

  • _R.tolua_commonclass = { }

  • _G.tolua = { type = tolua_bnd_type , takeownership = tolua_bnd_takeownership , releaseownership = tolua_bnd_releaseownership , cast = tolua_bnd_cast ,isnull = tolua_bnd_isnulluserdata , inherit = tolua_bnd_inherit }

  • 如果lua版本是5.1: _G.tolua.setpeer = tolua_bnd_setpeer ; _G.tolua.getpeer = tolua_bnd_getpeer ;

 

2: tolua_reg_types (lua_State* tolua_S) 为需要导出到lua中的c++类型注册元表

  • 对每一个要导出的c++类型调用tolua_usertype完成元表的注册。跟进tolua_usertype可以发现它做了两件事情。

    Step1: 调用 tolua_newmetatable 创建元表,并且给元表设置一系列的元方法。
    Step2: 调用 mapsuper( L , derived_type , base_type ) 在tolua_super表中以 derived_mt(子类型的元表)作为一个字段建立一张映射表 t , 这个t以父类,父类的父类(递归下去)的元表为键,布尔变量为值。 用伪代码可以表示为 tolua_super.derived_mt = { base_type = true , base_type_B = true , base_type_C = true} ,这样可以就可以判断两个类的继承关系。