从cocos2dx源代码看android和iOS跨平台那些事
cocos2dx一个跨移动(平板)平台的游戏引擎,支持2d和3d,基于c/c++,网上介绍多在此不详叙。
我们本篇关心的是跨平台那些事,自然而然就找到platform目录。好家伙,支持的操作平台还真不少,最吸引我们关注的可能就是字母顺序排列的头二个平台,android和apple。然后顺带会看一看winrt和linux。platform这个目录,物如其名,就是一些使用到平台服务的东西(封装)。platform目录下各平台子目录封装的平台服务都大同小异,唯android目录特殊还多了jni和java两个目录。因为在android平台下做应用,用c/c++的话还跨了语言。至于iOS,c/c++和oc混搭是很自然的事。所以我们来看android平台的跨语言那些事。
cocos/platform/android/jni,不用解释,就是java native interface。包含了从java到c/c++和从c/c++到java的调用协定。
这个目录最主要的就是JniHelper相关的头和源两个文件了。它主要负责帮助c/c++代码层完成对java代码层的静态方法的调用。而其它代码文件则是,特定为某个java类提供c/c++到java静态方法调用帮助,或是java到c/c++函数调用的帮助。
所以这个目录主要用来帮助c/c++代码调用java代码,而c/c++跨平台为java提供的功能(或服务)的函数,一般来说都分散到了具体模块的目录里,当需要支持跨平台的代码时,会将平台相关代码,写到如某个类的`-android.cpp`文件去。
另一个目录就是cocos/platform/android/java。没错就是java代码的目录。里面包含了一些专为cocos引擎层,也就是为让c/c++代码层使用到java库,或用java库实现功能的组件。可以通过java代码使用到android平台提供的服务。对于apple平台(iOS和mac)来说,c/c++代码与oc类混合使用是很自然的事;而winrt平台,平台提供的服务自然就是c/c++,或许以COM的形式;至于linux,第三方库都是c/c++库。因此除android以外的其它平台的子目录,并没有做太多的周折(相对于android来说)。
下面请看一下这样的比较:
// jni jclass _clazz = (jclass) env->CallObjectMethod(jobj, loadclassMethod_ID, jstrClassName); jclass _clazz = env->FindClass(_cstrClassName); // iOS Class _clazz = NSClassFromNSString(_nsstrClassName); Class _clazz = [ClassName class];
// jni jmethodID methodID = env->GetMethodID(_clazz, cstrMethodName, cstrParamCode); // iOS SEL _selector = NSSelectorFromString(_nsstrSELName); IMP imp = class_getMethodImplementation(_clazz, _selector);
// jni env->CallObjectMethod(jobj, methodID /**, (jobject*)arg1, ... */); // iOS objc_msgSend(obj, sel /**, arg1, arg2, ... */);
可以看到java和oc之间还有共通,现在从java看oc,还是从oc看java,都不会感到完全的陌生,反而有几分亲近。
有一点要注意的是,java的类名是全路径的,在代码中以点引用的方式对包引用,在名字中则以'/'为节点分隔的路径。java将方法拆分开方法名和原型描述。原型描述包含参数列表以及返回类型,参数列表以'()'包含放在前面,后面才是返回类型。对于java对象类型的描述包含在'L;'配对之中,java对象类型自然是类名的全路径了。'['表示的是数组。
jni从java调用c/c++代码也就可能这么一回事。java中只有类静态方法和成员方法,所以java要调用c/c++函数,就必须在java层有对应的方法入口(或者说可以让java代码调用的等价物,methodID),这样就将成员方法声明为native。从上面c/c++调用java的函数CallObjectMethod可以看出,java调用成员方法时也是根据methodID找对应的代码入口,jni生成的c/c++函数声明也就是java中对应声明为native的成员方法的methodID的映射。
当跨越jni时,相应也产生了损耗,其中原因只能去看jvm的代码了。在这里我只是试着猜想,可能c/c++函数和methodID不是直接映射,中间可能要做路由还是适配,或者是还有几层的处理,构建跨语言的栈帧环境等。另外可能就是,c/c++函数不是字节码指令的东西,不利于虚拟机对代码的优化,就好像处理器在分支预测失败的时候,要清空预装载的指令重新装入指令分支。