本文延续上一篇的思路,讨论如何在 cocos2dx 中让 C++ 回调 Lua 函数,从而实现事件机制。

相关阅读:cocos2dx 导出 C++ 类供 lua 使用

上一篇文章之所以绕弯处理,是因为当时没法直接调用 Lua 函数。这一次我们能直接调用 Lua 函数,实现思路就简单多了。

问题根源

问题的根本原因在于 tolua 工具自动生成的 hpp / cpp 文件中的函数签名不正确。虽然在 C++ 中把函数参数声明成了 LUA_FUNCTION,但实际上还是被当作 int 处理。

所以我们需要手动修改生成的函数,让它正确地把 Lua 端传过来的函数引用保存下来。

修改 tolua 生成的函数

把生成的对应函数体替换为下面的版本:

...
                
                生成的对应 代码 改为以下
                
                	if (NULL == tolua_S)
                		return 0;
                
                	int argc = 0;
                	NetMgr* self = nullptr;
                
                #if COCOS2D_DEBUG >= 1
                	tolua_Error tolua_err;
                	if (!tolua_isusertype(tolua_S, 1, "cc.Http", 0, &tolua_err)) goto tolua_lerror;
                #endif
                
                	self = static_cast<NetMgr*>(tolua_tousertype(tolua_S, 1, 0));
                #if COCOS2D_DEBUG >= 1
                	if (nullptr == self) {
                		tolua_error(tolua_S, "invalid 'self' in function 'tolua_cocos2d_Node_registerScriptHandler'\n", NULL);
                		return 0;
                	}
                #endif
                
                	argc = lua_gettop(tolua_S) - 1;
                
                	if (argc == 1)
                	{
                #if COCOS2D_DEBUG >= 1
                		if (!toluafix_isfunction(tolua_S, 2, "LUA_FUNCTION", 0, &tolua_err))
                			goto tolua_lerror;
                #endif
                
                		LUA_FUNCTION handler = toluafix_ref_function(tolua_S, 2, 0);
                		self->setLuaFunc(handler);
                		lua_settop(tolua_S, 1);
                		return 0;
                	}
                
                	luaL_error(tolua_S, "%s has wrong number of arguments: %d, was expecting %d\n", "cc.Node:registerScriptHandler", argc, 1);
                	return 0;
                
                #if COCOS2D_DEBUG >= 1
                tolua_lerror:
                	tolua_error(tolua_S, "#ferror in function 'tolua_cocos2d_Node_registerScriptHandler'.", &tolua_err);
                	return 0;
                #endif

头文件

对应的头文件定义如下,其中 toLuaFunc 作为基类提供 setLuaFunc / callLuaFunc 两个接口,NetMgr 继承它并封装 HTTP 请求:

class toLuaFunc/*base class for to call lun function*/
                {
                public:
                	void setLuaFunc(LUA_FUNCTION handler);
                
                
                protected:
                	void callLuaFunc(const char*ARG);
                
                	LUA_FUNCTION handler;
                
                };
                
                
                
                
                
                #define  URL_BASE "http://127.0.0.1:8080/cocos/"
                
                
                #define DATA_MAX_LENGTH 100
                
                class NetMgr :public toLuaFunc
                {
                public:
                	static NetMgr*getInstance();
                
                
                	/**
                	 * @brief new a HttpRequest
                	 * @param action  such as "login?name=1&&pass=1"
                	 * @
                	 */
                	void getRequest(const char* action);
                
                
                
                private:
                	NetMgr(){}
                	char _data[DATA_MAX_LENGTH];
                
                };

cpp 实现

对应的 cpp 实现如下。setLuaFunc 只是把 Lua 端传进来的 handler 记下来,callLuaFunc 则通过 LuaEngine 分发一个 kCommonEvent 事件去回调 Lua:

#include "network_srv.h"
                #include "CCLuaEngine.h"
                #include "base/CCScriptSupport.h"
                
                
                void toLuaFunc::setLuaFunc(LUA_FUNCTION handler)
                {
                	this->handler = handler;
                }
                
                void toLuaFunc::callLuaFunc(const char*ARG)
                {
                	CC_ASSERT(handler > 0, "call lua func 's handle must bigger than 0");
                
                	cocos2d::CommonScriptData data(handler, ARG);
                	cocos2d::ScriptEvent scriptEvent(cocos2d::kCommonEvent, &data);
                	cocos2d::LuaEngine::getInstance()->sendEvent(&scriptEvent);
                }
                
                
                void NetMgr::getRequest(const char* action)
                {
                	log("c++ getRequest arg is  %s",action);
                
                	string url = URL_BASE;
                	url += action;
                
                	HttpRequest*request = new HttpRequest;
                
                	request->setUrl(url.c_str());
                	request->setRequestType(HttpRequest::Type::GET);
                
                	request->setResponseCallback([=](HttpClient*client, HttpResponse *respone)
                	{
                		if (respone->getResponseCode() != 200)return;
                
                
                		vector<char>* buffer = respone->getResponseData();
                
                		CC_ASSERT(DATA_MAX_LENGTH > buffer->size(), " NetMgr buffer size overfloaw");
                
                		for (int i = 0; i < buffer->size(); i++)
                		{
                			_data[i] = (*buffer)[i];
                
                		}
                		_data[buffer->size()] = '\0';
                		callLuaFunc(_data);
                
                	});
                
                
                	HttpClient::getInstance()->setTimeoutForConnect(10);
                	HttpClient::getInstance()->send(request);
                	request->release();
                
                
                }
                
                
                
                NetMgr*  NetMgr::getInstance()
                {
                	static NetMgr* _netmgr__ = 0;
                	if (_netmgr__ == 0)
                	{
                		_netmgr__ = new NetMgr;
                	}
                	return _netmgr__;
                }

Lua 端调用

Lua 侧的用法很直接:拿到 NetMgr 单例后,先用 setLuaFunc 注册一个 Lua 回调函数,再在需要的时候调用 getRequest 触发 HTTP 请求,请求完成后 C++ 会回调这个 Lua 函数:

    local function menuCallbackClose()
                
                
                        http:getRequest("login?name=1&&pass=1")
                        return
                
                
                    end
                
                
                
                    label=cc.LabelTTF:create("login","",35)
                    label:setPosition(self.size.width/2,self.size.height/2);
                    rootNode:addChild(label)
                
                
                    local menuToolsItem = rootNode:getChildByName("Button_1")
                    menuToolsItem:addTouchEventListener(menuCallbackClose)
                
                 local function callback(msg)
                    label:setString(msg);
                 end
                
                    http=NetMgr:getInstance();
                    http:setLuaFunc(callback);

另一种 cpp 写法

其实 cpp 文件中的 callLuaFunc 还有另一种写法,直接通过 LuaStack 压栈并调用 handler,不走事件分发:

void toLuaFunc::callLuaFunc(const char*ARG)
                {
                	CC_ASSERT(handler > 0, "call lua func 's handle must bigger than 0");
                /*
                	cocos2d::CommonScriptData data(handler, ARG);
                	cocos2d::ScriptEvent scriptEvent(cocos2d::kCommonEvent, &data);
                	cocos2d::LuaEngine::getInstance()->sendEvent(&scriptEvent);
                */
                
                	cocos2d::LuaEngine::getInstance()->getLuaStack()->pushString(ARG);
                	cocos2d::LuaEngine::getInstance()->getLuaStack()->executeFunctionByHandler(handler, 1);
                	cocos2d::LuaEngine::getInstance()->getLuaStack()->clean();
                
                
                
                }