弱者才会相信运气,强者只相信因果

0%

Cocos 客户端开发中的三座桥(其二) JSB

JSB(Javascript Binding)

JSB 是第二座桥,它与 JNI 类似,是提供 Native 层与 Javascript 层互相访问的一种方式,支持双向调用。

上面一章节提到了普通的 Android 程序架构,下面的是 Cocos 程序的架构,它是这样的:

在 Java 之上又通过 JNI 回到了 Cocos 的 Native 层,此层是通过动态链接库.so 加载的。然后再经过一个 JSB 的桥与 Javascript 进行交互。游戏则是使用 Javascript 编写的。

JSB 有好几种,有 Mozilla 家的 SpiderMonkey 和 Google 家的 V8,最新的 iOS 里则用到了 Apple 自家的 JSB 接口。使用哪种 JSB 接口主要取决于使用哪种 Javascript 虚拟机。

排除掉各种 JS 虚拟机的不同,Cocos 为我们提供了统一的接口,让我们可以使用。它能实现 Javascript 中的一个方法与一个 C 方法进行绑定。在 Javascript 中调用该方法时,则在 C++中被绑定的方法会被调用。

1、Javascript 调用 C++

如果我们在 C++中看见这样的函数名 js**_(se::State& s)那么它就是为 JSB 而写的了。它不像 JNI 一样有严格的命名规范,可以时任意函数名,但为了可以方便快捷的定位方法,所以人为的定了这么一个命名规范。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
static bool js_cocos2dx_FileUtils_addSearchPath(se::State& s)
{
cocos2d::FileUtils* cobj = (cocos2d::FileUtils*)s.nativeThisObject();
SE_PRECONDITION2(cobj, false, "js_cocos2dx_FileUtils_addSearchPath : Invalid Native Object");
const auto& args = s.args();
size_t argc = args.size();
CC_UNUSED bool ok = true;
if (argc == 1) {
std::string arg0;
ok &= seval_to_std_string(args[0], &arg0);
SE_PRECONDITION2(ok, false, "js_cocos2dx_FileUtils_addSearchPath : Error processing arguments");
cobj->addSearchPath(arg0);
return true;
}
if (argc == 2) {
std::string arg0;
bool arg1;
ok &= seval_to_std_string(args[0], &arg0);
ok &= seval_to_boolean(args[1], &arg1);
SE_PRECONDITION2(ok, false, "js_cocos2dx_FileUtils_addSearchPath : Error processing arguments");
cobj->addSearchPath(arg0, arg1);
return true;
}
SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 2);
return false;
}
SE_BIND_FUNC(js_cocos2dx_FileUtils_addSearchPath)

这时一段典型的 JSB 代码,通过函数名我们就知道了这个 JSB 绑定的是 FileUtils 的 addSearchPath 方法。

addSearchPath 是个重载函数,一个有 1 参数,一个有 2 参数。我们可以看到 JSB 中是以 逻辑 来区分函数重载的。

乍一看,JSB 也辣么变态,1 行代码变成 27 行。如果我只有一个函数绑定 JSB 还好,如果我有成百上千和函数要绑定,岂不是这辈子都要搭里面了?Noooooo…

好在 Cocos 又提供的方案,就是自动生成 JSB 代码。只需要简(bìng)单(bù)配置一个配置文件,然后让生成脚本跑一下,那成百上千的 JSB 绑定函数就变出来了。

在我们的项目中,我将这个工具放在了 tools/bindings-generator 下。配置文件是 test/test.ini,生成脚本是 test/test.py。

不细讲了,自己看吧。

2、C++ 调用 Javascript

不推荐使用 C++调用 Javascript,因为需要明确地写明方法名来找到该 JS 方法并调用。而 C++是编译语言不支持热更,Javascript 则是脚本语言可以作为资源进行热更新。这很容易造成 Js 中的方法名修改后导致调用失效。

解决方案就是将 Js 方法作为参数传入 C++中,在 C++中调用该注册方法。上面提到的 bindings-generator 支持生成将函数作为参数的代码,好好利用吧。