本文延续上一篇的思路,讨论如何在 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();



}