1.背景知识
1.1什么是JNI
JNI是Java Native Interface的简写,简单来说,就是一种JAVA和C++之间相互调用的一套接口。利用JNI可以在JAVA代码层(manager code)调用C++代码层(native code)的函数,反之亦可。
如果你之前并不熟悉JNI,可以通过官方文档:Java Native Interface Specification ( http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html)先来了解一下。
英文不好的同学,可以看看这篇博客(http://wiki.jikexueyuan.com/project/deep-android-v1/jni.html)
1.2 为什么要用JNI
Cocos2d-x作为一种跨平台的开源游戏引擎,其特点是一次编写,到处部署。如果部署到Android平台,我们可以利用JNI来调用Android的一些类库和控件,加个Android原生对话框什么的。更重要的是,如果项目里有很多组件是用JAVA编写的,我们应该尽量使用现有的组件,避免重复的制造轮子。
应当指出,Cocos2d-x可以调用Android的控件,但是不应该过分依赖这一点,唯一用到这一功能的情景,是引擎实在没法实现,从解耦的角度考虑,界面逻辑应当尽量放在Cocos当中来做。
2.Cocos2d-x调用JAVA代码
Cocos为我们封装了一些JNI调用的接口,这个类叫JniHelper(其实JAVA自己也提供了JNI调用的接口类,叫JniHelp,这两个类没什么差别),这个类的位置在[cocos安装路径]\cocos\platform\android\jni
中
在Cocos中用JNI调用JAVA程序时,jni.h这个头文件是必须的,好在JniHelper.h中已经帮我们包含了,所以我们只需要包含JniHelper.h这个头文件就可以了。
所以,在需要调用JNI的类中,需要加入
#if(CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
#include "platform/android/jni/JniHelper.h"
#endif
条件编译,是用来说明这个东西是Android平台上用到的。
JniHelper.h当中还包含一个相当重要的结构体:JniMethodInfo,看名字就猜到了,这个结构体是用来定位调用的JAVA方法的,来看看这个结构体的定义
typedef struct JniMethodInfo_
{
JNIEnv * env;
jclass classID;
jmethodID methodID;
} JniMethodInfo;
classID和methodID很好理解,分别代表类和方法,这样就可以定位到具体的方法了。env稍微有点复杂,它表示一个线程相关的结构体,里面保存的是JNI函数指针,不能跨线程访问,其实也不用管这个变量究竟代表什么意思,用起来十分简单。
那么下一个问题是怎么初始化这三个成员变量,或者说怎么注册JAVA方法。
举个例子,待调用的JAVA方法,位于org.cocos2dx.cpp包中,类名叫TestJni,待调用的方法是func1
(关于如何将Cocos项目部署到Eclipse上,参考我前一篇博客:http://blog.csdn.net/sgn132/article/details/50481923)
TestJni.java的代码十分简单,注意这里func1方法是静态函数,具体代码如下:
package org.cocos2dx.cpp;
import android.util.Log;
public class TestJni {
public static void func1(){
Log.d("success","java jni called succeed");
}
}
然后打开Cocos项目,我这里使用VS2015作为开发环境的,cocos版本是v3.8.1,在一个按键响应方法里,添加调用JNI的代码
#if(CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
JniMethodInfo info;
//getStaticMethodInfo判断java定义的静态函数是否存在,返回bool
bool ret = JniHelper::getStaticMethodInfo(info,"org/cocos2dx/cpp/TestJni","func1","()V");
if(ret)
{
log("call void func1() succeed");
//传入类ID和方法ID,小心方法名写错,第一个字母是大写
info.env->CallStaticVoidMethod(info.classID,info.methodID);
}
#endif
- getStaticMethodInfo是注册函数
他有4个参数,前三个很好理解,用来定位JAVA方法,最后一个参数一般称为签名,是用来表示调用的JAVA方法的传递参数和返回值类型的。 - CallStaticVoidMethod是调用函数
这个函数也不用多说了,使用来调用指定JAVA方法的。
2.1 JNI怎么传递自定义数据类型
2.1.1 参数、返回值类型组成的字符串——签名
首先要提的是JNI的签名机制,签名其实就是函数的参数类型和返回值类型共同组成的字符串,之所以要有签名机制,是因为C++是允许函数重载的,单靠函数名无法准确定位函数的入口,还需要函数的参数信息。
JNI的规范签名格式是:
(参数1类型标示参数2类型标示...参数n类型标示)返回值类型标示。
举个栗子:
JAVA中函数定义为
void play(String singer,String album)
对应的签名
(Ljava/lang/String;Ljava/lang/String;)V
其中L
表示引用类型,后面跟的是包名,需要把”.“换成”/“,V
表示void。
这里给出标示类型对照表:
标示类型 | Java类型 |
---|---|
Z | boolean |
B | byte |
C | char |
S | short |
I | int |
J | long |
F | float |
D | double |
L/java/lang/String; | S |