cocos2dx-详细剖析lua(如何与lua集成,如何导出lua api,如何与lua交互)

本文详解lua是怎么跟c/c++交互的;cocos怎么利用luac/c++交互的技术,导出lua api的供脚本使用;cocos如何进行c++与lua混合编程,cocos有哪些重要lua接口。

一、lua与c、c++的交互

1、lua简介

lua是由c语言编写,c/c++程序可以包含lua库,利用提供的api进行lua脚本开发,lua提供了c与lua相互调用的接口。下面是lua的全部库文件:

lapi.c ldo.h lmathlib.c lstate.c lua.h
lapi.h ldump.c lmem.c lstate.h luaconf.h
lauxlib.c lfunc.c lmem.hlstring.clualib.h
lauxlib.h lfunc.h loadlib.clstring.hlundump.c
lbaselib.c lgc.c lobject.clstrlib.clundump.h
lcode.c lgc.h lobject.h ltable.c lvm.c
lcode.h linit.c lopcodes.c ltable.h lvm.h
ldblib.c liolib.c lopcodes.hltablib.clzio.c
ldebug.c llex.c loslib.cltm.clzio.h
ldebug.h llex.h lparser.cltm.hprint.c
ldo.c llimits.h lparser.h lua.c

这些文件中lua_开头的api是c api,luaL_开头的是auxiliary library,其它的是Lua functions

2、c、c++调用lua

可以利用lua编写一些代码,然后供c++调用,可以用lua编写配置文件,从中提取信息。下面是lua代码,描述了app的配置信息以及一个测试函数:

-- app config 
config = {
	version = "1.0",
	name = "myApp",
	date = "2016/6/25"
	author = "lewis" 
}

-- for function test
function add(x, y)
	return x + y
end
下面是c++访问lua的内容:

void LuaTest::btnClick_c_call_lua(CCObject* pSender, CCControlEvent event){
    //创建Lua状态机
    lua_State *L = luaL_newstate();
    
    //加载并执行Lua文件
    string path = CCFileUtils::sharedFileUtils()->fullPathForFilename("config.lua");
    if(luaL_dofile(L, path.c_str()))
    {
        cout<<"luaL_dofile error"<<endl;
        return ;
    }
    
    //读取table
    /*version = "1.0",
    name = "myApp",
    date = "2016/6/25"
    author = "lewis"*/
    lua_getglobal(L, "config");//config变量进入栈顶索引-1
    lua_getfield(L,-1,"version");//config.version进入栈顶索引-1,config索引变成-2
    const char *version = lua_tostring(L, -1);//提取config.version为c中字符串
    lua_pop(L, 1);//弹出config.version,config索引变成-1
    
    lua_getfield(L,-1,"name");
    const char *name = lua_tostring(L, -1);
    lua_pop(L, 1);
    
    lua_getfield(L,-1,"date");
    const char *date = lua_tostring(L, -1);
    lua_pop(L, 1);
    
    lua_getfield(L,-1,"author");
    const char *author = lua_tostring(L, -1);
    lua_pop(L, 1);
    
    lua_pop(L, 1);//弹出config,栈中没有元素了
    printf("version=%s\nname=%s\ndate=%s\nauthor=%s\n", version, name, date, author);
    
    //5.读取函数
    lua_getglobal(L, "add");        //add变量进入栈顶索引-1
    lua_pushnumber(L, 10);          //压入第一个参数,add变量进入栈顶索引-2
    lua_pushnumber(L, 20);          //压入第二个参数,add变量进入栈顶索引-3
    
    //调用lua中函数add,弹出2个参数还有add这个变量,其中2表示参数个数,1表示返回结果个数
    //最后返回值会压栈
    if (lua_pcall(L, 2, 1, 0))
    {
        const char *pErrorMsg = lua_tostring(L, -1);
        cout << pErrorMsg << endl;
        lua_close(L);
        return ;
    }
    
    if (lua_isnumber(L, -1))        //检查结果类型 lua中数值类型是double表示的
    {
        double result = lua_tonumber(L, -1);
        printf("10 + 20 = %f", result);
    }
    
    lua_pop(L, 1);//弹出结果,可以不做,下面关闭状态机后栈会销毁
    
    //关闭状态机,销毁栈
    lua_close(L);
    
    return ;
}
输出:

version=1.0
name=myApp
date=2016/6/25
author=lewis
10 + 20 = 30.000000
lua执行时,要先创建一个lua状态,lua状态包含了lua栈,lua栈用来让c与lua通信,c通过lua提供的c api,把lua中的变量放到栈中,调用lua函数,获得lua变量值,设置lua变量值等操作。进行所有的c操作都是针对栈顶的,例如lua_getglobal、lua_getfield、lua_pushnumber都是往栈顶写元素。lua_tostring这种lua_toTYPE函数是获得lua中变量对应的c类型变量值。lua提供的api执行后的返回值不为0就是出错了,可以用来检查是否程序出错。luaL_dofile是个宏分两步,一步加载lua代码生成字节码,一步lua_pcall执行代码,就是把代码从头到尾跑一边,对于定义就是分配内存,对于函数调用就是执行函数。lua的详细api可以去官网看看:https://www.lua.org/manual/5.1/

3、lua调用c、c++

lua调用c,所提供的c函数签名必需是typedef int (*lua_CFunction) (lua_State *L);这种类型。编写好函数后,只需向lua的lua_State L注册一下就OK了,之后使用L执行的lua代码都可以访问在L注册的c函数。看上面定义,好像lua函数不好传参数给c函数啊!其实你可以在lua函数传任意参数,它会写到L的栈中,前面曾说过c与lua是通过一个栈通信的。

当lua函数带调用提供了N个参数时,lua最终调用的c函数可以通过上面讲的c/c++调用lua里面提供的接口提取出参数,进行计算,最后再把结果push到栈中返回给lua,详细图解如下:


图片地址:https://i-blog.csdnimg.cn/blog_migrate/e61be4e8f3d17b92814e3f21a396b6d9.gif

下面是代码,在lua中创建一个CCLabelTTF加到当前场景中。

int cAPI_addLabel(lua_State *L){
    printf("stack size:%d\n", lua_gettop(L));
    
    //获得位置x、y
    double y = lua_tonumber(L, -1);
    double x = lua_tonumber(L, -2);
    
    //create label
    CCString *str = CCString::createWithFormat("hello world\n x:%f y%f", x, y);
    Label *label = Label::create(str->getCString(), BIG);
    label->setPosition(ccp(x, y));
    CCDirector::sharedDirector()->getRunningScene()->addChild(label);
    
    lua_pushlightuserdata(L, label);//把指针传给lua
    
    return 1;
}

void LuaTest::btnClick_lua_call_c_2(CCObject* pSender, CCControlEvent event){
    //新建状态
    lua_State *L = luaL_newstate();
    
    luaL_openlibs(L);//we will use lua io api
    
    //向lua注册c函数
    lua_register(L, "cAPI_addLabel", cAPI_addLabel);
    
    //加载并执行脚本
    string path = CCFileUtils::sharedFileUtils()->fullPathForFilename("luacallc.lua");
    luaL_dofile(L, path.c_str());
    
    //关闭状态
    lua_close(L);
}
点击下面按钮出现最下面的标签并显示它的x、y值:

图片地址:https://i-blog.csdnimg.cn/blog_migrate/8270c165f8f16fab8894b7bc8267c161.gif
 

输出:

stack size:2
result:	userdata: 0x7a37c400
这里点击按钮后调用 LuaTest ::btnClick_lua_call_c_2( CCObject * pSender, CCControlEvent event),它会使用lua_register(L,"cAPI_addLabel", cAPI_addLabel);注册函数cAPI_addLabel,然后加载执行luacallc.lua这个文件,内容如下:

local x = cAPI_addLabel(320, 200)

print("result:", x)
上面代码创建标签,并把它加到当前场景,并输出它的返回值,返回值类型是userdata,就是一个包装了指针的类型,输出的值跟指针值一样。

函数注册后执行lua文件,lua里调用了cAPI_addLabel这个函数,lua首先会把L中的栈清空,然后把参数从左往右压栈,这也是上面x、y的获取是

 先double y =lua_tonumber(L, -1);再double x =lua_tonumber(L, -2);因为y在栈顶-1位置,x在下面-2的位置,c函数cAPI_addLabel取出栈中2个元素来设置标签位置。设置完后把标签对象的指针压栈,然后返回1,告诉lua引擎我这个函数返回1个返回值,然后返回到lua代码的执行,它从栈顶往下取1个数给result,最后输出result这个userdata元素的值。这里看出lua个c是如何通过栈交换数据的,c从栈中取数据是我们手动编码,lua从栈中取数据是lua引擎执行的。每次lua调c函数L的栈会清空,然后压入参数这个要注意,获取的返回值个数是根据c函数返回值确定的,这个也要注意下。

二、利器tolua++

1、cocos怎么把tolua++导出的c api给lua调用的

上面的lua调用c/c++已经展示了lua调用c函数并且传参数给它设置cocos对象位置,但是如果我们都这样编码,对于一个cocos当中的类,比如CCSprite有好多个函数,我们要编写符合lua调用的c接口,那得先从栈中取出参数,然后把参数传给对应的c++函数,执行后再把c++的函数结果压栈,这些操作在的cAPI_addLabel函数中可以找到,此外还要进行检查,万一lua那边传来的参数类型不对呢,就得用lua_isTYPE来判断类型,那工程量有点大啊。好在我们有工具tolua++,利用它可以轻松导出c api供lua调用。

下面是CCLabelTTF向lua注册的代码在LuaCocos2d.cpp里面可以找到

  tolua_beginmodule(tolua_S,"CCLabelTTF");
   tolua_function(tolua_S,"new",tolua_Cocos2d_CCLabelTTF_new00);
   tolua_function(tolua_S,"new_local",tolua_Cocos2d_CCLabelTTF_new00_local);
   tolua_function(tolua_S,".call",tolua_Cocos2d_CCLabelTTF_new00_local);
   tolua_function(tolua_S,"delete",tolua_Cocos2d_CCLabelTTF_delete00);
   tolua_function(tolua_S,"init",tolua_Cocos2d_CCLabelTTF_init00);
   tolua_function(tolua_S,"setString",tolua_Cocos2d_CCLabelTTF_setString00);
   tolua_function(tolua_S,"getString",tolua_Cocos2d_CCLabelTTF_getString00);
   tolua_function(tolua_S,"getHorizontalAlignment",tolua_Cocos2d_CCLabelTTF_getHorizontalAlignment00);
   tolua_function(tolua_S,"setHorizontalAlignment",tolua_Cocos2d_CCLabelTTF_setHorizontalAlignment00);
   tolua_function(tolua_S,"getVerticalAlignment",tolua_Cocos2d_CCLabelTTF_getVerticalAlignment00);
   tolua_function(tolua_S,"setVerticalAlignment",tolua_Cocos2d_CCLabelTTF_setVerticalAlignment00);
   tolua_function(tolua_S,"getDimensions",tolua_Cocos2d_CCLabelTTF_getDimensions00);
   tolua_function(tolua_S,"setDimensions",tolua_Cocos2d_CCLabelTTF_setDimensions00);
   tolua_function(tolua_S,"getFontSize",tolua_Cocos2d_CCLabelTTF_getFontSize00);
   tolua_function(tolua_S,"setFontSize",tolua_Cocos2d_CCLabelTTF_setFontSize00);
   tolua_function(tolua_S,"getFontName",tolua_Cocos2d_CCLabelTTF_getFontName00);
   tolua_function(tolua_S,"setFontName",tolua_Cocos2d_CCLabelTTF_setFontName00);
   tolua_function(tolua_S,"create",tolua_Cocos2d_CCLabelTTF_create00);
   tolua_function(tolua_S,"create",tolua_Cocos2d_CCLabelTTF_create01);
   tolua_function(tolua_S,"create",tolua_Cocos2d_CCLabelTTF_create02);
   tolua_function(tolua_S,"create",tolua_Cocos2d_CCLabelTTF_create03);
  tolua_endmodule(tolua_S);
这些代码以及后面贴的代码都是tolua++生成的,对上面最后注册的4个函数进行分析,lua中的函数名都是create,对应不同的c函数,其实lua只会调用里面的一个,至于到底调用哪个c函数,还得在c函数中分析参数决定。还有好多其它函数名字也是create,那都注册为名为create的函数不就出问题了,要指定lua里函数也是变量,它可以存在一个全局的表中,后面有时间把上面的lua调c/c++补全,少了个调c++。lua里面CCLabelTTF:create(arg1, arg2, arg3)这种调用可以改写成CCLabelTTF.create(CCLabelTTF, arg1, arg2, arg3),调用的是表CCLabelTTF的函数,然后传入CCLabelTTF, arg1, arg2, arg3四个参数。下面是cocos生成CCLabelTTF表的代码:

tolua_beginmodule(tolua_S,"CCLabelTTF");
TOLUA_API void tolua_beginmodule (lua_State* L, const char* name)
{
    if (name)
    {
        lua_pushstring(L,name);
        lua_rawget(L,-2);
    }
    else
        lua_pushvalue(L,LUA_GLOBALSINDEX);
}

lua_pushstring(L,name);压栈,lua_rawget(L,-2)等价于lua_gettable(L, -2)【Similar to lua_settable, but does a raw assignment (i.e., without metamethods).】

这里lua_pushstring后栈中应该一个元素呀,怎么还有-2呢?查看下面代码:

/* Open function */
TOLUA_API int tolua_Cocos2d_open (lua_State* tolua_S)
{
 tolua_open(tolua_S);
 tolua_reg_types(tolua_S);<pre name="code" class="cpp">/* method: create of class  CCLabelTTF */
#ifndef TOLUA_DISABLE_tolua_Cocos2d_CCLabelTTF_create03
static int tolua_Cocos2d_CCLabelTTF_create03(lua_State* tolua_S)
{
 tolua_Error tolua_err;
 if (
     !tolua_isusertable(tolua_S,1,"CCLabelTTF",0,&tolua_err) ||
     !tolua_isnoobj(tolua_S,2,&tolua_err)
 )
  goto tolua_lerr
  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值