分析在cocos2dx中lua调用c++类的细节

以上篇为例,编写一个pkg文件,接下来单独解析这个pkg文件

创建一个新的批处理脚本,命名为TestBuild.bat,输入以下内容

@echo off
:begin
set/p a1=请输入pkg文件名称并按回车:
rem echo 你输入的pkg文件名称是:%a1%,如果确认请按y
set/p a2=你输入的pkg文件名称是:%a1%,如果确认请按y并回车:

if %a2% NEQ y goto begin
D:\cocos2d\cocos2d-x-2.2.2\cocos2d-x-2.2.2\tools\tolua++\tolua++.exe -o %a1%.cpp %a1%.pkg  
pause

运行此文件,输入上一篇文件的pkg文件的名称TestSprite.pkg,如果在当前目录看到生成了一个TestSprite.cpp,则说明已经单独为这个pkg文件生成了一个中间转换类。


cocos2d-x2.2.2中是用tolua++第三方库来专门处理LUA脚本,它可以很好的帮助开发者完成lua访问c++类的成员函数的功能。


先看下lua用传统的方式访问c++的类。

首先,用lua调用c++的成员函数是以函数指针的形式进行调用,即要满足以下类型的函数才能被lua调用

typedef int (*lua_CFunction) (lua_State *L);

其中lua_State用于存储栈中的信息,返回函数返回值个数的一个整形值,返回值的内容存在栈中,用lua_State取。

测试cpp如下:

#include <string>
#include <math.h>
#include <iostream>
using namespace std;

extern "C" {  
#include <lua.h>  
#include <lualib.h>  
#include <lauxlib.h>  
} 
lua_State* l = NULL;
//求总和
static int addSum(lua_State* l) {
	int n = lua_gettop(l);//获取栈顶的编号,即栈中的个数
	double sum = 0;
	for(int i = 1; i <= n; i++) {
		if(!lua_isnumber(l, i)) {//存在非数字的跳过,并提示错误
			lua_pushstring(l, "Not a Number");  //压入字符串到栈
            lua_error(l);  
		}else {
			sum += lua_tonumber(l, i);
		}
	}
	lua_pushnumber(l, sum);//将相加的结果压入栈中
	lua_pushnumber(l, n);//将相加数也压入栈中

	return 2;//两个返回值
}
//主函数
int main(int argc, char *argv[]) {
	//初始化lua_State
	l = lua_open();
    //打开lua库
	luaL_openlibs(l);
	//注册c++函数供lua调用("addSum" -- lua调用的名字, addSum--自定义函数的函数指针)
	lua_register(l, "addSum", addSum);
	//执行一个脚本
	luaL_dofile(l, "test.lua");
	//下面是用c++取出lua执行完函数后的结果
	//获取脚本变量sum的值,放入栈顶
	lua_getglobal(l, "sum");
	//从栈顶取值
	cout<<"c++: The sum is "<<lua_tointeger(l, -1)<<endl;
	//从栈顶弹出一个值
	lua_pop(l, 1);
	//获取脚本变量n的值,放入栈顶
	lua_getglobal(l, "n");
	//从栈顶取值
	cout<<"c++: The num is "<<lua_tointeger(l, -1)<<endl;
	//回收内存
	lua_close(l);

	return 0;
}

lua脚本

sum, n = addSum(1, 2, 3, 4)
print("lua: The sum is", sum)
print("lua: The num is", n)

运行结果:


以上的过程说明了要用lua调用c++的成员函数必须要进行栈操作,当一个类的成员函数一庞大的话处理起来就会相当麻烦。


接上一篇的内容,看下用tolua++执行完自定义的pkg文件后LuaCocos2d.cpp新增的内容

打开LuaCocos2d.cpp搜索自定义的类,如TestSprite,发现找到了以下代码

tolua_usertype(tolua_S,"TestSprite");
找到执行这行代码的方法,如下
/* function to register type */
static void tolua_reg_types (lua_State* tolua_S)
借助注释,由这个方法体可以推断出任何可供被lua调用的类都应该要在这个方法中注册。

再往下搜索,找到下面的一个方法

/* method: create of class  TestSprite */
#ifndef TOLUA_DISABLE_tolua_Cocos2d_TestSprite_create00
static int tolua_Cocos2d_TestSprite_create00(lua_State* tolua_S)
...
再找到这个方法的调用的(搜索tolua_Cocos2d_TestSprite_create00),看到如下代码

  tolua_cclass(tolua_S,"TestSprite","TestSprite","CCSprite",NULL);
  tolua_beginmodule(tolua_S,"TestSprite");
   tolua_function(tolua_S,"create",tolua_Cocos2d_TestSprite_create00);
   tolua_function(tolua_S,"init",tolua_Cocos2d_TestSprite_init00);
   tolua_function(tolua_S,"setPosCenter",tolua_Cocos2d_TestSprite_setPosCenter00);
   tolua_function(tolua_S,"createLabel",tolua_Cocos2d_TestSprite_createLabel00);
  tolua_endmodule(tolua_S);
由此可以推断上面的那个方法是一个转换方法,即lua要调用自定义类的函数时进行的一些压栈出栈等操作,而且上面也列出了自定义类可供lua调用的方法,每个方法对应一个转换函数。再看执行上面那段代码的方法。

/* Open function */
TOLUA_API int tolua_Cocos2d_open (lua_State* tolua_S)
借助注释可以看出就是c++暴露给lua调用的函数,而且是模块的形式进行。

再回过头来看下tolua_Cocos2d_TestSprite_create00这个转换函数

/* method: create of class  TestSprite */
#ifndef TOLUA_DISABLE_tolua_Cocos2d_TestSprite_create00
static int tolua_Cocos2d_TestSprite_create00(lua_State* tolua_S)
{
#ifndef TOLUA_RELEASE
 tolua_Error tolua_err;
 if (
     !tolua_isusertable(tolua_S,1,"TestSprite",0,&tolua_err) ||
     !tolua_isnoobj(tolua_S,2,&tolua_err)
 )
  goto tolua_lerror;
 else
#endif
 {
  {
   TestSprite* tolua_ret = (TestSprite*)  TestSprite::create();
    tolua_pushusertype(tolua_S,(void*)tolua_ret,"TestSprite");
  }
 }
 return 1;
#ifndef TOLUA_RELEASE
 tolua_lerror:
 tolua_error(tolua_S,"#ferror in function 'create'.",&tolua_err);
 return 0;
#endif
}
#endif //#ifndef TOLUA_DISABLE
首先是判断参数是否合法,不合法跳过解析块,直接跳到tolua_lerror处,否则tolua++执行转换解析。
再看下tolua_pushusertype这个方法,打开cocos2d引擎的根目录\scripting\lua\tolua\tolua_push.c,找到tolua_pushusertype方法的定义

TOLUA_API void tolua_pushusertype (lua_State* L, void* value, const char* type)
{
    tolua_pushusertype_internal(L, value, type, 0);
}
再跳到tolua_pushusertype_internal,
void tolua_pushusertype_internal (lua_State* L, void* value, const char* type, int addToRoot)
{
    if (value == NULL)
        lua_pushnil(L);
    else
    {
        luaL_getmetatable(L, type);                                 /* stack: mt */
        if (lua_isnil(L, -1)) { /* NOT FOUND metatable */
            lua_pop(L, 1);
            return;
        }
        lua_pushstring(L,"tolua_ubox");
        lua_rawget(L,-2);                                           /* stack: mt ubox */
        if (lua_isnil(L, -1)) {
            lua_pop(L, 1);
            lua_pushstring(L, "tolua_ubox");
            lua_rawget(L, LUA_REGISTRYINDEX);
        };
        
        lua_pushlightuserdata(L,value);                             /* stack: mt ubox key<value> */
        lua_rawget(L,-2);                                           /* stack: mt ubox ubox[value] */
        
        if (lua_isnil(L,-1))
        {
            lua_pop(L,1);                                           /* stack: mt ubox */
            lua_pushlightuserdata(L,value);
            *(void**)lua_newuserdata(L,sizeof(void *)) = value;     /* stack: mt ubox value newud */
            lua_pushvalue(L,-1);                                    /* stack: mt ubox value newud newud */
            lua_insert(L,-4);                                       /* stack: mt newud ubox value newud */
            lua_rawset(L,-3);                  /* ubox[value] = newud, stack: mt newud ubox */
            lua_pop(L,1);                                           /* stack: mt newud */
            /*luaL_getmetatable(L,type);*/
            lua_pushvalue(L, -2);                                   /* stack: mt newud mt */
            lua_setmetatable(L,-2);                      /* update mt, stack: mt newud */
            
#ifdef LUA_VERSION_NUM
            lua_pushvalue(L, TOLUA_NOPEER);             /* stack: mt newud peer */
            lua_setfenv(L, -2);                         /* stack: mt newud */
#endif
        }
        else
        {
            /* check the need of updating the metatable to a more specialized class */
            lua_insert(L,-2);                                       /* stack: mt ubox[u] ubox */
            lua_pop(L,1);                                           /* stack: mt ubox[u] */
            lua_pushstring(L,"tolua_super");
            lua_rawget(L,LUA_REGISTRYINDEX);                        /* stack: mt ubox[u] super */
            lua_getmetatable(L,-2);                                 /* stack: mt ubox[u] super mt */
            lua_rawget(L,-2);                                       /* stack: mt ubox[u] super super[mt] */
            if (lua_istable(L,-1))
            {
                lua_pushstring(L,type);                             /* stack: mt ubox[u] super super[mt] type */
                lua_rawget(L,-2);                                   /* stack: mt ubox[u] super super[mt] flag */
                if (lua_toboolean(L,-1) == 1)                       /* if true */
                {
                    lua_pop(L,3);                                   /* mt ubox[u]*/
                    lua_remove(L, -2);
                    return;
                }
            }
            /* type represents a more specilized type */
            /*luaL_getmetatable(L,type);             // stack: mt ubox[u] super super[mt] flag mt */
            lua_pushvalue(L, -5);                    /* stack: mt ubox[u] super super[mt] flag mt */
            lua_setmetatable(L,-5);                /* stack: mt ubox[u] super super[mt] flag */
            lua_pop(L,3);                          /* stack: mt ubox[u] */
        }
        lua_remove(L, -2);    /* stack: ubox[u]*/
        
        if (0 != addToRoot)
        {
            lua_pushvalue(L, -1);
            tolua_add_value_to_root(L, value);
        }
    }
}
可以看出tolua_pushusertype确实是一个转换函数,里面执行了lua调用c++函数时所需要的栈操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值