关于 JSB2.0 实践中的一些问题,C++ 调用 JS 在 se::AutoHandleScope hs 引发异常

是这样的,我们有个棋牌项目是 lua 的,网络通信这块用的原生 Socket。现在有个新的换皮换玩法项目,我打算用 Creator 重构。而服务器暂时还是 Socket。所以我打算通过 JSB 把原来项目的C++ 的 Socket 暴露给 JS 层。这样的话以后如果服务器换 socket.io 的话,我就重写一下 Socket 这块就能发 H5 了。

问题

  1. 这样的话会不会有什么我考虑不到的问题

  2. 我按照官网文档操作,用 VS Windows下调试,JS 成功调用了 C++ 的 Socket 方法,而在 C++ 调用 JS 回调的时候出现了异常。搞不懂是怎么回事,代码如下


/*
 * SKSocket.cpp
 */

...

void SK::SKSocket::connect(const char* server, int port, const std::function<void(const string&)>& cb)
{
	_serverAddress = Socket::getIpAddress(server);
	_socketLogic->openWithIp(_serverAddress.c_str(), port, true);
	_connectCB = cb;
}

void SK::SKSocket::onConnected()
{
	CCLOG("联网成功");
	_connected = true;
        if (_connectCB != nullptr)
	{
	        _connectCB("OK");
	}
}

...

static bool js_SKSocket_connect(se::State& s)
{
	const auto& args = s.args();
	int argc = (int)args.size();
	if (argc >= 2)
	{
		SK::SKSocket* cobj = (SK::SKSocket*)s.nativeThisObject();

		se::Value jsServerAddress = args[0];
		se::Value jsServerPort = args[1];
		se::Value jsFunc = argc > 2 ? args[2] : se::Value::Undefined;
		se::Value jsTarget = argc > 3 ? args[3] : se::Value::Undefined;

		if (jsFunc.isNullOrUndefined())
		{
			cobj->connect(jsServerAddress.toString().c_str(), jsServerPort.toInt32(), nullptr);
		}
		else
		{
			assert(jsFunc.isObject() && jsFunc.toObject()->isFunction());

			// 如果当前 SomeClass 是可以被 new 出来的类,我们 使用 se::Object::attachObject 把 jsFunc 和 jsTarget 关联到当前对象中
			//s.thisObject()->attachObject(jsFunc.toObject());
			//s.thisObject()->attachObject(jsTarget.toObject());

			// 如果当前 SomeClass 类是一个单例类,或者永远只有一个实例的类,我们不能用 se::Object::attachObject 去关联
			// 必须使用 se::Object::root,开发者无需关系 unroot,unroot 的操作会随着 lambda 的销毁触发 jsFunc 的析构,在 se::Object 的析构函数中进行 unroot 操作。
			// js_cocos2dx_EventDispatcher_addCustomEventListener 的绑定代码就是使用此方式,因为 EventDispatcher 始终只有一个实例,
			// 如果使用 s.thisObject->attachObject(jsFunc.toObject);会导致对应的 func 和 target 永远无法被释放,引发内存泄露。

			jsFunc.toObject()->root();
			if (jsTarget.isObject())
			{
				jsTarget.toObject()->root();
			}

			cobj->connect(jsServerAddress.toString().c_str(), jsServerPort.toInt32(), [jsFunc, jsTarget](const string& counter) {

				// CPP 回调函数中要传递数据给 JS 或者调用 JS 函数,在回调函数开始需要添加如下两行代码。
				se::ScriptEngine::getInstance()->clearException();
				se::AutoHandleScope hs;  //** 这里出现异常* *

				se::ValueArray args;
				args.push_back(se::Value(counter));

				se::Object* target = jsTarget.isObject() ? jsTarget.toObject() : nullptr;
				jsFunc.toObject()->call(args, target);
			});
		}

		return true;
	}

	SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", argc, 2);
	return false;
}
SE_BIND_FUNC(js_SKSocket_connect)

...

/*
 * HelloJsb.ts
 */

...

onLoad() {
        cc.log("HelloJsb onLoad");
        new SK.SKSocket().connect("x.x.x.x", 8888, ()=>{
            cc.log("JSSocket.connect success");
        });
    }

...

这里出现异常:

se::AutoHandleScope hs;

异常信息:

0x03B6794B (v8.dll)处(位于 jsb2test.exe 中)引发的异常: 0xC0000005: 读取位置 0x00000E48 时发生访问冲突。 出现了

@jare @panda @dumganhar

搞定了,没事了

:joy:

我把对 JS 的回掉放到了计时器里就解决了问题,即把上面的 onConnected 改成下面:

void SK::SKSocket::onConnected()
{
	CCLOG("联网成功");
	_connected = true;
	Director::getInstance()->getScheduler()->schedule([this](float dt) {
		if (_connectCB != nullptr)
	        {
	                _connectCB("OK");
	        }
	}, this, 0, 0, 0, false, "keyConnectCB");
}

那么问题来了,这是为啥呢?

另外,我把我的 Socket 和模拟器一起重新编译了,结合脚本与编辑器跑起来没有问题。问一下用模拟器能不能直接在 JS 源文件下断点调试,而不是 project.dev.js 里。

你这个回调函数不是主线程吧?

嗯,不是主线程,应该怎么调用呢,不太了解c++的线程

在子线程用 cocos2d::Director::getInstance()->getScheduler()->performFunctionInGLThread() 包裹一下转发到 cocos 线程执行任务。

好方法