cocos 绑定到lua时指定命名空间 程序崩溃

引用: http://www.cocoachina.com/bbs/read.php?tid=226362
--------------------------------------------------------------------------------

一、bindings-generator

当有一些感觉狂拽炫酷屌的 C or C++ 函数库想加到专案里时,难免会用上 cocos2d-x 自带的 bindings-generator 产出库让 lua 调用。

在 3.0 rc0 以前的说明并不像稳定版本这么“比较”多一些,花在 trial and error 上的时间也就相对的多了许多。过去的就说好不提了,单从现在稳定版的简单描述流程:

1. 依照 bindings-generator 里 README.md 中下载并安装所会用上的工具,工具版本如果在 README 中有指定,麻烦请辛苦一些找到指定版本。要不,如果出了什么灵异现象,请不要怪罪这个工具怎么这么不科学……
2. 参考 tools/bindings-generator/test 中的test.ini 设定,为自己要产出的函数库量身订做变更设定内容。

3. 如果函数库中有自订的 namespace(内地是称这个为啥?命名空间?!),请打开tools/bindings-generator/targets/lua/conversions.yaml,并在 ns_map 内容中补上自己的 namespace。要小心的是该内容仅吃空白字元,如果习惯用 TAB 来做缩排的请留意。

4. 有需求的话就请变更 batch 档,可以让输出资料名称与路径指定到您想要的地方。当然,在 unix 环境下请变更 shell 档。

5. 执行 batch 档!理论上来说应该就会看到一个 header 与 cpp 档,就可以拿来挂载了。


注意事项

1.  bindings-generator 到 3.0 final  版本为止, 使用 c++11 的 functional 与 lambda function 用来做函数的参数或回传都是不行的 !并不是说执行输出的时候会错,而是会产生空的内容。

嗯,拿 cocos2d-x 自己的玩意来当例子:

在 EventDispatcher 类中有一个函数是这么被定义的:

EventListenerCustom* addCustomEventListener(const std::string &eventName, const std::function<void(EventCustom*)>& callback);

绑定 lua 绑定函数内容会输出到 lua_cocos2dx_auto.cpp 中,底下是实际的片段:

int lua_cocos2dx_EventDispatcher_addCustomEventListener(lua_State* tolua_S)
{
    .... 太长了,省略之 ....
    
    argc = lua_gettop(tolua_S)-1;
    if (argc == 2) 
    {
        std::string arg0;
        std::function<void (cocos2d::EventCustom *)> arg1;

        ok &= luaval_to_std_string(tolua_S, 2,&arg0);

        do {
// Lambda binding for lua is not supported.
            assert(false);
        } while(0)
        ;
        if(!ok)
            return 0;
        cocos2d::EventListenerCustom* ret = cobj->addCustomEventListener(arg0, arg1);
        object_to_luaval<cocos2d::EventListenerCustom>(tolua_S, "cc.EventListenerCustom",(cocos2d::EventListenerCustom*)ret);
        return 1;
    }
}


那一行  [Lambda binding for lua is not supported. 内容整个亮了....QAQ

要是有非得使用 callback 函数不然会死的需求的话,还请改用 oldschool 的作法吧!

2.  *.ini 设定档中每一个设定都是有他的特殊用意,请不要完全依样画葫芦式的全搬到自己函数库的设定里,请一定要适度的调整。

举个例子,在原先 test.ini 的最末:

# 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 = yes

在要挂载的函数库里有控制对象的生成与销毁的话麻烦这选项请填 no ,尤其是有写什么管理者来管理对象的更是要留意。

3.  请善用 conversions.yaml,这玩意儿是个好物!有自定义类要做 c to lua or lua to c 相互转换的话,可以利用这个来做。当然,要转换的函数还是要先行提供好才行,这部分属于比较进阶的玩意,但是用的好的话可以节省不少 handcode 的时间。

cocos2d-x 3.0rc0 - bindings-generator 问题与解决  

这是先前针对 3.0 rc0 时所写的一点小吐槽,里头有简单的相关设定,烦请参考。

--------------------------------------------------------------------------------

二、挂载自定义函式库

本来呢,以为将产生好的头文件与源码放到想摆放的位置,再参考 lua-tests  里如何挂载自定义函式库到 lua 就成了,但是……

天底下哪有这么便宜的事!

先看一下 lua-tests  的  AppDelegate.cpp  中的相关内容:

    // register lua engine
    LuaEngine* pEngine = LuaEngine::getInstance();
    ScriptEngineManager::getInstance()->setScriptEngine(pEngine);
    
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID ||CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC)
    LuaStack* stack = pEngine->getLuaStack();
    register_assetsmanager_test_sample(stack->getLuaState());
#endif


很单蠢,喔不,是看起来再单纯不过了咩。只要把那个  register_assetsmanager_test_sample( )  替换成自订库产生出来的 register_all_ooxx_bindings( )  不就行了?

可以试试,不过应该是程序崩溃才是。原因?让我们看下去……

嗯,底下问题解说的会有些生硬,如果只想看解决方法的话可直接跳过,毕竟不影响大局嘛。

问题:

LuaEngine  被初始时会建立起  LuaStack  对象,而真正 lua 栈对象  luaStack  的建立、挂载 cocos2d-x 自己的 modules、读入一些外部库(如 luasock、lsqlite)这些行为都是在  LuaStack  被建立起时一并完成的。

流程大概是这样:

LuaEngine::getInstance() → new LuaEngine → LuaEngine::init() → LuaStack::create() → LuaStack::init()

可以打开 CCLuaStack.cpp 瞧一下,位置约莫在 cocos/scripting/lua-bindings/manual/ 底下,看一下  LuaStack::init()  函数,就会明白上头再说些什么东西来着。

话说回来,这怎么会造成挂载自己库时程序崩溃呢?

为了处理 2.x <-> 3.0 之间转换的问题,在 LuaEngine::init()  时执行了三个 .lua:

bool LuaEngine::init(void)
{
    _stack = LuaStack::create();
    _stack->retain();
    executeScriptFile("DeprecatedEnum.lua");
    executeScriptFile("DeprecatedClass.lua");
    executeScriptFile("Deprecated.lua");

    return true;
}


问题就出在执行  executeScriptFile()  后,为了让 lua 的栈能够被正常的再使用,执行完后都会将 lua 栈归零。

int LuaEngine::executeScriptFile(const char* filename)
{
    int ret = _stack->executeScriptFile(filename);
    _stack->clean();    // 这里会归零,就是很简单的调用 lua_settop(_state, 0)
    return ret;
}


归零并不是错误的!问题点是在处理完后要挂载自己输出库的时间点,摆个简单转换后要挂载的函数的内容看一下:

TOLUA_API int register_all_ooxx_bindings(lua_State* tolua_S)
{
    tolua_open(tolua_S);
    tolua_module(tolua_S,"ooxx",0);
    tolua_beginmodule(tolua_S,"ooxx");
    lua_register_ooxx_bindings_TalkComponent(tolua_S);
    tolua_endmodule(tolua_S);
    return 1;
}


如果什么事都不处理,按照前文提到的仿照 lua-test  AppDelegate.cpp  一样把自己的库挂上去:

    // register lua engine
    LuaEngine* pEngine = LuaEngine::getInstance();
    ScriptEngineManager::getInstance()->setScriptEngine(pEngine);
    LuaStack* stack = pEngine->getLuaStack();
    register_all_ooxx_bindings(stack->getLuaState());


执行之……就会发现死在 tolua_module( )  !一般而言,应该都是死在 tolua_module( )  里的  lua_rawget(L,-2)

TOLUA_API void tolua_module (lua_State* L, const char* name, int hasvar)
{
    if (name)
    {
        /* tolua module */
        lua_pushstring(L,name);
        lua_rawget(L,-2);    // <- 会死在这里!
        if (!lua_istable(L,-1))  /* check if module already exists */


为什么?

熟悉 lua c API 的看后应该就知道问题点。 想去取得 -2 index 的栈值,但此时栈内仅有一个字串,其他完全是空的,不死才是不科学!

整个整理一下,会挂掉的原因出于执行了三个标记已过时便于转换用的  .lua chunk file ,然后 lua 栈被归零,最后要挂载自订库,然后……就没有再然后了……

解决:

有两个方法可以解决。

一个是先前随便乱回答的偷机方法:打开 LuaEngine.cpp,把那三行执行 .lua 的注解掉,最后把执行那三个 .lua 的移到挂载完自己的函数库后再执行就成了。

传送门,请按

详细可看上头连结,虽然写得有些啰唆……

第二个法子单纯点,既然栈是空的那就给他资料不就得了:

    auto engine = LuaEngine::getInstance();
    ScriptEngineManager::getInstance()->setScriptEngine(engine);
    auto luaStack = engine->getLuaStack()->getLuaState();
    if (luaStack)
    {
        lua_getglobal(luaStack, "_G");
        register_all_ooxx_bindings(luaStack);
        lua_settop(luaStack, 0);
    }


就这样,简单吧 :)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值