|
编辑注:CocoaChina2013春季开发者大会,于4月13日顺利举办,这里GameRes游资网作为合作媒体,为大家发送会议上的经验访谈。
下午技术探讨分会场首先是Cocos2D-X核心开发者张小明,他主要讲了三个方面的内容,包括Javascript绑定、Plugin和代码安全。Javascript绑定我打算从以下五个方面来讲,包括C++如何执行一个Javascript脚本,C++调用Javascript函数,Javascript如何调用C++ 函数绑定,C++类到Javascript和CXX Generator。以下是演讲实录:
张小明:大家好,我是Cocos2D-X的张小明。
今天主要想跟大家讲三个方面的内容,包括Javascript绑定、Plugin和代码安全。Javascript绑定我打算从以下五个方面来讲,包括C++如何执行一个Javascript脚本,C++调用Javascript函数,Javascript如何调用C++ 函数绑定,C++类到Javascript和CXX Generator。
张小明正在分享Cocos2d-x案例演示:Javascript绑定、集成第三方库及代码安全策略
C++执行Javascript脚本文件,在我们的软件里面绑定在Object下,执行完之后可以删除一些资源。旁边列出了一些函数,就是主要做这些工作的。等一下我们可以看一下具体的例子,我这边每一个都有具体的例子,而且我的例子里面都上传到网上了。
这是用来执行Javascript脚本的。这个参数的含义就是分配8兆内存,刚达到8兆的时候,Javascript机芯开始工作。创建一个Context8192,是一个比较好的值,大概就是说可以嵌入到多少层级,大家使用8192这个默认值就可以。接下来设置了一些变量,创建一个Global Object。我们这边的脚本是Hello World Cocos2d,大家到时候可以下载我的参数代码,就可以执行这个脚本,最后一行是整个脚本的返回值。从这里我们可以看一下,打印出来的结果应该是Hello World Cocos2d,然后我把Cocos2d去掉,让大家看看是否是Hello World,给大家演示一下,这边就是一个Hello World,通过这几个步骤就可以执行一个Javascript脚本。
接下来讲一下C++调用Javascript函数,前面的步骤是一样的,首先需要执行Javascript脚本。执行完Javascript脚本,这边不同之处在于Javascript函数触发是由C++调用过去的,通过上面列出的三个函数调用Javascript,这三个函数的功能是类似的,只不过第三个参数有点不同。从名字可以看出来,一个CallFunction,还有CallFunctionName,CallFunctionValue。这个例子里面就是Global Object,还有就是参数个数、参数的数据,最后有一个返回值。接下来做了什么?前面一样的,创建执行的欢迎,创建Global Object,创建初始函数。
接下来我们看一下Javascript的代码,第一个函数是GSFunction,通过函数打印出来。从这个代码来看,传入的函数是可以转为String的一个参数,最后变成Javascript脚本。上面不用管,是判断对象是否有这个属性,在我们这边是有。最主要的就是通过JSCallFunctionName,通过一个名字调用。Javascript脚本和C++脚本之间的交互基本上是通过一个JSValue的变量传输,Javascript脚本传输给C++,C++脚本传输给Javascript,都是通过JSValue这个脚本,和JS一样,有自己的一套规则。我们这边传了一个JS String的类型,这边打印的是JSFunction,会把Hello World打印出来,得出的结果就是Hello World Cocos2d加上去,这样也演示了如何传递,只不过参数变了而已。
Javascript如何调用C++函数。主要有三个步骤,首先也是一样,要初始化那些环境,接下来通过一个JSDefineFunction,注册到Javascript,Javascript就可以知道这个函数,就可以通过这个函数去调用。但是想一想,如果Javascript想调用一个C++函数,他应该知道什么东西呢?函数名字肯定是跑不掉的,还有函数个数。函数类型其实不是很关键的,因为在Javascript代码里面是没有类型这个概念的。所以说,假设参数类型不正确,它照样会传递过去,但是里面需要自己去做判断。我们再看一下,其实刚才的例子里面就已经包含了如何调用C++函数,大家看一下那个Log函数。这个Log函数不是Javascript原来就有的,这个Log函数其实是我们注册到Javascript。通过JSDefineFunction去注册C++函数,挂在Global下,触发Log函数的时候调用本地的Log函数,传输个数是1,这个方法不允许被删除。然后看一下这个Log是怎么定义的,Javascript调用C++函数,这个回传的Log是固定的,传一个执政,接下来是参数个数。
刚才说了,C++和JS交互,都是通过JSValue数据类型进行交互的。还有一个函数可以直接把Javascript传递出来的参数直接解析到对应的数据类型,这边的类型会解释成一个JSString,或者直接转化成一个IND。我们把这个值打印出来,刚才已经看到了Log的效果,我们就不再看了,主要是看一下流程大概是怎么样的。
如何绑定C++类到JS。从上面这个路来看有点复杂,大家想一想,如果C++想知道一个类的话需要知道哪些信息呢?名字肯定是少不了,还有构造函数,会有一个虚构函数的调用,于是注册那里边有哪些成员变量,包括静态、非静态的,这些信息全部都要注册到Javascript那边。从上面来看,是通过JS-InitClass注册到一个C++类,JS里面需要有一个JSClass,主要注册了两个内容,包括类的名字,还有就是需要有一个回调函数,其他的可以忽略,就是基本的默认值。这边有一个Prototype,比如说父类的类型是什么,这边是放入了一个父类的JSObject。这边是属性,有对应的名字,会触发相应的方法调用。在我们的例子当中没有演示,因为在我们绑定的代码中,基本上是不太建议直接使用变量去访问的,而是通过方法调用,这样可以达到比较好的控制。Function静态和非静态的其实是一样的,需要一个名字。当这个Function被调用的时候,需要调用一个本地的回调函数,就是Native Wrapper。
接下来我们看一下同样的例子,前面的步骤都是一样的,环境的建立。在初始化完标准类之后,就可以进行C++类的注册,这个顺序一定要注意,如果是在这个标准函数之前注册直接会崩溃。我们看一下做了哪些事情,首先我为了演示继承关系注册了一个父类和一个子类,父类叫Father,子类叫Son,Father很简单,只有一个函数,Son继承于Father,返回值,返回一个ID类型,还有一个静态成员函数Function3,当这些方法被调用的时候,都会触发相应的信息打印。有两个参数值,来验证传入的参数是否正确,然后把返回值返回出去,来验证返回值是否正确。接下来看是怎么注册的,注册类型,Father和Son基本上是一样的,因为Son比Father为了一步就是父类。这边需要一个JSClass,最主要的就是它的名字Son,还有当被虚构的时候,调用一个被虚构的函数,就是删除多余的一个对象。在这个例子当中比较偷懒,就是只创建了一个对象,也就是说每次构成函数被调用的时候,都创建一个对象。在这个开发过程当中,这需要是一一对应的,就是你的C++对象和生成出来的JS对象需要一一对应,能够互相找到对方。所以说我这个例子当中,如果创建一个以上的对象,就会复制过去,新构成一个虚构的,这个只是为了简化流程,否则会增加一些额外代码,但是不影响整个功能的实现。
接下来看一下注册成员方法,有一个Function2,还有一个静态成员方法Function3,这两个数据结构其实是一样的,只不过它们被传到JS InitClass,对应的参数不一样而已。第一个是注册到Global,看一下构造函数创建了一个Son对象,创建出一个对应的JS对象,把这个JS对象返回到Javascript那一层。成员函数就是刚才说的Son Function,静态成员变量就是刚才说的Function3。
Javascript代码,首先创建了一个Son对象,然后调用Function1,是在父类实现的,就是Father实现的。再接下来调用Father2,把返回值存到Result,因为在Function2的时候,会打印传进来参数的值,从而验证返回结果是否正确。接下来再调用一个静态成员方法Function3,我来运行一下。Function1被调用,接下来Function2被调用,传进来的两个参数值是A、1和1,这时候Result是2,传进去,两个参数值是2和3,这也是正确的。最后再调用静态成员方法打印出来,Function3,最后是Son is Destroyed。我们随便改一个词,看一下是否正确,传进来的参数值是3和1,第二个参数值是4和3,说明我们传入的参数的值以及返回结果都是正确的。
CXX-Generator,就是自动生成绑定脚本的一个工具,是通过一个Clang的前端,编辑器去解析,就可以自动生成绑定代码。你传进来的有可能依赖一些标准库,有可能是非常多的类,这些类我是不希望绑定到的,这个就去配置有哪个不需要绑定。这个Template Files就是使得这个自动生成的脚本更具备模块化。因为刚才看,无论是绑定的类还是绑定的方法,都是基本上一个步骤的,都是有规律可的。要生成的代码都放到一个Template Files里面,以后假定我绑定代码出错了,我只需要去改动Template Files,根本不需要去绑定CXX-Generator。
我们来看一个例子,前面的一些代码大家都已经很熟悉了,这是Cocos2D-X,Javascript Sample里面基本上都是一样的,我们这边不同的就是多了一个注册,自己生成的绑定代码的回调函数,这个回调函数就是用于注册的。我们可以看一下要绑的类信息,比较简单,否则生成的代码会比较不好看。我们用一个比较简单的类,这个方法获取的就是这个值,接下来的Log就是用于打印的,刚才说过。我们看一下它的实现,首先这个函数对成员变量进行初始值,这边是5个1,把这个变量值返回回去,Log打印出来。我们的Javascript代码页比较简单,生成了一个Simple Native Class对象,把这个值打印。自动生成代码绑定可以看一下,大概是这个样子。首先也需要有一个注册,刚才说的注册类,这是一样的,这个都是自动生成的,注册了一个叫Simple Native Class的类,其他的都不用讲了,跟刚才的是一样的。我们来看一下最终的结果怎么样,我们改成2,然后看一下,执行的结果是222。接下来我们看一下,我如果改动的C++代码,假设不叫Log,叫Print,实现的方法同样改成Print。这个时候我们再运行的时候肯定报错,因为绑定代码的时候,他不知道有一个叫Print的函数。我们可以看到,它是绑一个Log,这边就是说Log不是一个函数,那么我们需要通过刚才的绑定脚本自动生成绑定代码。这边有一个Tools,这些Tools我也都上传了。只要运行一个简单的脚本,它就自动生成绑定,我们再看一下编辑结果。这个时候编辑结果应该是可以通过的,但是Javascript那边应该会报错,因为它的函数没改,这边会显示Value False。如果改成Print,就可以打印出222。这样通过这个自动绑定脚本工具,就可以非常轻易的对你改动的代码进行自动绑定。Cocos2D-X之所以每次C++版本代码,Javascript这边又很稳定,就是因为有这个工具,它自动生成。刚才讲到ini,有Headers就是头文件的路径,有Classes,还有Skip,还有Rename,假如说C++的名字刚才叫Log,我Javascript不想叫Log,我叫Print没问题,那个名字也一样可以改。当然还有更多的配置,在文件里面都有详细的说明。
接下来讲一下Plugin,早晨王哲大概讲了Plugin-X,主要是为了方便集成第三方库,抽取一些抽象的接口。第三方库只要按照这样的接口,开发者就可以很方便的调用,不管底下是哪个第三方库。我们可以看一下它的架构,上面是游戏逻辑代码,最底下是第三方插件,中间就是我们Plugin的S,大家可以看到有各种各样我们抽先出来的接口,有统计借口、有AIP,有广告。在Android上面有对应的C++,有对应的Java代码,Plugin Manager就是用于管理这些第三方插件的,我们可以通过Plugin Manager去加载和卸载,大家对Plugin最主要的就是通过Plugin Manager,可以产生第三方查检的对象。这个对象通过上面Protocol去转成对应的广告,比如说是友盟还是别的,通过传了之后就可以调用对的方法。
Plugin怎么使用呢?基本上只要运行两个脚本,就可以在游戏里面使用。第一个叫Publish.sh,第二个是gameDevGuide.sh。Publish做哪些事情呢?绿色的部分是实现对接口的代码,这些C++代码,或者是Java代码需要编成.1和.2,在你的游戏工程里面去使用,把你需要的头文件拷贝到一个Publish目录,以及刚才生成的一些摘要库。DevGuide主要是做什么呢?就是直接对你的工程改配置,比如说你需要使用第三方插件的话,你需要去改动和链接这些库,你在HTML5文件需要改动,比如说需要配置一些权限,有些可能需要访问网络,这些就自动帮你做这些事情。你只需要在游戏逻辑代码里面使用早晨王哲贴出来的那些代码就可以,其他不需要关心里面的复杂程度。
假设Plugin-X没有你需要的那些Plugin,你需要开发自己的Plugin。开发Plugin是怎么做的?首先有三个要求:一是目录结构,就是需要跟现有的Plugin-X保持一致。为什么?因为刚才我们说的,那些工具会自动帮你去改那些配置。但是它对目录结构是有要求的,它对待目录结构是有定式的,否则他找不到你对应的工程的配置文件。二是实现对应的Protocol,目前需要实现有C++代码,有Java代码。但是需要在未来大家只需要提供对Java代码的对接和对Object的对接,就不需要再区市县其他的东西了。三是需要添加Android、iOS工程。我们可以简单看一下现在的目录结构,底下列出来的网址就是我们给出的教程,具体如何去实现一个Plugin。大家可以简单看一下Plugin的目录结构。比如说以这个为例,这边有头文件,有分平台实现,有iOS、Android,刚才说的目录结构一致就是这个意思,大概就是这样的目录。这里面就存放了一些实现代码。详细的信息可以看我列出的文档,就在官方文档里面的Wiki里。
代码安全。主要是向大家介绍几种方法。如何保护你的Java代码?有些人可能觉得Java代码不需要保护。但是有开发者向我们反馈,他们的APK直接被拿去,直接插入对应的Java代码,插入自己的广告,插入自己的收费,再重新打个包再发出去,而你的游戏就为他人服务。Java代码如何保护呢?我们这边给出了一个界定,首先你在编译的时候对Java代码进行计算,把这个值保存在一个C++的头文件中。C++运行的时候,先去计算这个Java代码的值,和保存在头文件当中的对比,假设已经出错了,说明你的Java代码已经被改变了,你可以实现相应的措施。代码加密,这主要是针对脚本,在我们的Cocos2D-X里面,Sample里面基本上没有对代码加密,因为代码使用哪种加密方式,每个游戏可能有不同的需求。但是你可以对代码进行加密,在运行过程中先对太揭秘,再把解密之后的那些脚本直接扔给引擎去运行。接下来是代码混淆,因为Javascript代码是很容易混淆的,假设代码加密又被破解了怎么办?还有一个办法就是Javascript代码混淆,把Javascript代码混淆到看不懂的程度。我们这边是通过Google提供的工具,可以对你的Java代码进行混淆,但是不影响你的游戏逻辑。底下贴出了一个链接,就是讲如何做具体混淆的,大概步骤是这样的,就是你首先需要提供一个文件,就是一个配置文件,你需要混淆哪些Javascript代码,哪些Javascript代码又不需要混淆,同时也可以排除。在这里要强调一下,最好绑定出去的Javascript代码不要混淆,因为如果混淆之后,找不到对的函数的。因为Javascript代码被混淆之后,它是通过名字去调用C++函数,如果被混淆之后,它的名字可能被替换,那么C++是没有对应的函数的,所以说就会调用不到。我们这边还提供了一个工具,用于自动生成HTML5文件,需要注意一点,那个文件对于声明的Javascript的顺序是有要求的,它的顺序要和Javascript代码里面使用的Requre是一致的,假如说A Requre B,就是放在前面,否则的话会出现错误。我们大概看一下混淆过后的代码怎么样,这是在我们的Cocos2D-X Sample的。这边有一个混淆过后的,我想这个应该是能够阅读出来的,如果能阅读出来是很厉害了,这就是混淆之后我们的测试脚本的代码。大家有兴趣的话可以去看一下,在Javascript这个测试的例子里面有两个Task,一个是混淆之前的,一个是混淆之后的。
我今天就跟大家分享这些内容,谢谢! |
|