cocos2d-x 通过JNI实现c/c++和Android的java层函数互调

转自:http://codingnow.cn/cocos2d-x/992.html

文章摘要:本文主要实现两个功能: (1)通过Android sdk的API得到应用程序的包名(PackageName),然后传递给c++层函数。 (2)通过c++函数调用Android的java层函数,显示一个对话框,点击按钮退出程序。1. 首先来简单学习一下JNI的相关知识,我这篇文章中简单实现了怎么在Android Java层调用c++函数。要想使用JNI,必须得…

本文主要实现两个功能:
(1)通过Android sdk的API得到应用程序的包名(PackageName),然后传递给c++层函数。
(2)通过c++函数调用Android的java层函数,显示一个对话框,点击按钮退出程序。

1. 首先来简单学习一下JNI的相关知识,我这篇文章中简单实现了怎么在Android Java层调用c++函数。要想使用JNI,必须得包含头文件,android是使用ndk编译c/c++的,这里jni.h文件位于:\android-ndk-r8b\platforms\android-14\arch-arm\usr\include\jni.h,该文件定义了所有和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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# include <inttypes.h>      /* C99 */
typedef  uint8_t         jboolean;        /* unsigned 8 bits */
typedef  int8_t          jbyte;           /* signed 8 bits */
typedef  uint16_t        jchar;           /* unsigned 16 bits */
typedef  int16_t         jshort;          /* signed 16 bits */
typedef  int32_t         jint;            /* signed 32 bits */
typedef  int64_t         jlong;           /* signed 64 bits */
typedef  float            jfloat;          /* 32-bit IEEE 754 */
typedef  double           jdouble;         /* 64-bit IEEE 754 */
#else
typedef  unsigned  char    jboolean;        /* unsigned 8 bits */
typedef  signed  char      jbyte;           /* signed 8 bits */
typedef  unsigned  short   jchar;           /* unsigned 16 bits */
typedef  short            jshort;          /* signed 16 bits */
typedef  int              jint;            /* signed 32 bits */
typedef  long  long        jlong;           /* signed 64 bits */
typedef  float            jfloat;          /* 32-bit IEEE 754 */
typedef  double           jdouble;         /* 64-bit IEEE 754 */
#endif
/* "cardinal indices and sizes" */
typedef  jint            jsize;
#ifdef __cplusplus
/*
* Reference types, in C++
*/
class  _jobject {};
class  _jclass :  public  _jobject {};
class  _jstring :  public  _jobject {};
class  _jarray :  public  _jobject {};
class  _jobjectArray :  public  _jarray {};
class  _jbooleanArray :  public  _jarray {};
class  _jbyteArray :  public  _jarray {};
class  _jcharArray :  public  _jarray {};
class  _jshortArray :  public  _jarray {};
class  _jintArray :  public  _jarray {};
class  _jlongArray :  public  _jarray {};
class  _jfloatArray :  public  _jarray {};
class  _jdoubleArray :  public  _jarray {};
class  _jthrowable :  public  _jobject {};
typedef  _jobject*       jobject;
typedef  _jclass*        jclass;
typedef  _jstring*       jstring;
typedef  _jarray*        jarray;
typedef  _jobjectArray*  jobjectArray;
typedef  _jbooleanArray* jbooleanArray;
typedef  _jbyteArray*    jbyteArray;
typedef  _jcharArray*    jcharArray;
typedef  _jshortArray*   jshortArray;
typedef  _jintArray*     jintArray;
typedef  _jlongArray*    jlongArray;
typedef  _jfloatArray*   jfloatArray;
typedef  _jdoubleArray*  jdoubleArray;
typedef  _jthrowable*    jthrowable;
typedef  _jobject*       jweak;
#else /* not __cplusplus */
/*
* Reference types, in C.
*/
typedef  void *           jobject;
typedef  jobject         jclass;
typedef  jobject         jstring;
typedef  jobject         jarray;
typedef  jarray          jobjectArray;
typedef  jarray          jbooleanArray;
typedef  jarray          jbyteArray;
typedef  jarray          jcharArray;
typedef  jarray          jshortArray;
typedef  jarray          jintArray;
typedef  jarray          jlongArray;
typedef  jarray          jfloatArray;
typedef  jarray          jdoubleArray;
typedef  jobject         jthrowable;
typedef  jobject         jweak;
#endif /* not __cplusplus */

我们经常用到的是JNIEnv*,它是一个c结构体,封装了许多常用的函数,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct  _JNIEnv {
     /* do not rename this; it does not seem to be entirely opaque */
     const  struct  JNINativeInterface* functions;
#if defined(__cplusplus)
     jint GetVersion()
     return  functions->GetVersion( this ); }
     jclass DefineClass( const  char  *name, jobject loader,  const  jbyte* buf,
         jsize bufLen)
     return  functions->DefineClass( this , name, loader, buf, bufLen); }
     jclass FindClass( const  char * name)
     return  functions->FindClass( this , name); }
// 这里省略其他函数...
}

cocos2d-x引擎对jni的操作进行了封装,提供了一个非常好用的类:JniHelper,定义了一些常用的接口,该文件位于cocos2dx/platform/android/jni目录下。下面看看JniHelper.h源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typedef  struct  JniMethodInfo_
{
     JNIEnv *    env;
     jclass      classID;
     jmethodID   methodID;
} JniMethodInfo;
class  CC_DLL JniHelper
{
public :
     static  JavaVM* getJavaVM();
     static  void  setJavaVM(JavaVM *javaVM);
     static  const  char * getExternalAssetPath();
     static  void  setExternalAssetPath( const  char * externalAssetPath);
     static  jclass getClassID( const  char  *className, JNIEnv *env=0);
     static  bool  getStaticMethodInfo(JniMethodInfo &methodinfo,  const  char  *className,  const  char *methodName,  const  char  *paramCode);
     static  bool  getMethodInfo(JniMethodInfo &methodinfo,  const  char  *className,  const  char *methodName,  const  char  *paramCode);
     static  std::string jstring2string(jstring str);
private :
     static  JavaVM *m_psJavaVM;
     static  std::string m_externalAssetPath;
};

下面来解释JniHelper的两个常用函数:
(1)getStaticMethodInfo
用来判断Java的类静态函数是否存在,并初始化结构体JniMethodInfo,该结构体封装了JNIEnv*和java.lang.Class对象、函数ID。这样就可以使用JNIEnv*调用 CallStaticXXXMethod(jclass clazz, jmethodID methodID, …)和 CallXXXMethod(jobject obj, jmethodID methodID, …)等常用函数(XXX替换为函数返回值类型,如:Void,Int等)。
第一个参数为JniMethodInfo,第二个参数是类的绝对路径,第三个参数是函数名,第四个参数是函数签名(参数和返回类型),示例代码如下:

1
2
3
4
if (JniHelper::getStaticMethodInfo(t, CLASS_NAME,  "showTipDialog" "(Ljava/lang/String;Ljava/lang/String;)V" ))
{
//...
}

关于类型签名,请对照下图:

(2)getMethodInfo
该函数与getStaticMethodInfo类似,用于Java类的非静态函数。

2. 下面开始实现文章开头所述的两个功能,本文是在cocos2d-x 2.0版本 自适应屏幕分辨率demo的基础上添加的。
(1)利用cocos2d-x创建一个Android工程,名为JniTest,包名为com.alexzhou.jni,此时该包下会自动生成一个JniTest.java文件。
(2)首先来实现把应用程序的包名传递给c++函数,在包下创建JniTestHelper.java,该类封装了给c++调用的函数,添加如下代码:

1
2
3
4
5
6
7
8
private  static  Handler mHandler;
public  static  void  init(Handler handler)
{
     JniTestHelper.mHandler = handler;
}
public  static  native  void  setPackageName(String packageName);

(3)打开JniTest.java,在onCreate函数中添加下面的代码:

1
2
3
4
5
protected  void  onCreate(Bundle savedInstanceState){
         super .onCreate(savedInstanceState);
         JniTestHelper.init(mHandler);
         JniTestHelper.setPackageName( this .getPackageName());
     }

(4)java层的代码已经完成了,下面来编写jni层代码,在/jni/hellocpp/下创建test.h和test.cpp文件,test.h文件暂时不添加任何函数,代码如下:
test.h

1
2
3
4
#ifndef TEST_H
#define TEST_H
#endif

test.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "cocos2d.h"
#include <jni.h>
#include "platform/android/jni/JniHelper.h"
#include "test.h"
#include "JniTest.h"
#define CLASS_NAME "com/alexzhou/jni/JniTestHelper"
using  namespace  cocos2d;
extern  "C"
{
void  Java_com_alexzhou_jni_JniTestHelper_setPackageName(JNIEnv *env, jobject thiz, jstring packageName)
{
     const  char  *pkgName = env->GetStringUTFChars(packageName, NULL);
     setPackageName(pkgName);
     env->ReleaseStringUTFChars(packageName, pkgName);
}
}

必须加上extern “C”,声明以c语言的方式进行编译,因为c++和c在编译时生成的函数签名不一样,可以在网上查找相关资料,不然运行的时候会出现链接错误。
(5)现在编写c++函数,在Classes目录下创建JniTest.h,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef JNI_TEST_H
#define JNI_TEST_H
#include "cocos2d.h"
using  namespace  cocos2d;
void  setPackageName( const  char  *packageName)
{
     CCLog( "packageName: %s" , packageName); 
}
#endif

(6)修改jni/Android.mk文件的LOCAL_SRC_FILES值 ,内容如下:

1
2
LOCAL_SRC_FILES := hellocpp /main .cpp \
                    hellocpp /test .cpp

(7)编译运行,因为我是使用cygwin编译的,而且Android项目不在cocos2d-x的根目录下,所以需要修改build_native.sh,修改COCOS2DX_ROOT和NDK_MODULE_PATH的值,把当前cocos2d-x项目的路径添加到NDK_MODULE_PATH,修改后的值:

1
2
3
COCOS2DX_ROOT= "/cygdrive/e/cocos2d-x/cocos2d-2.0-x-2.0.4"
"NDK_MODULE_PATH=${COCOS2DX_ROOT}:${COCOS2DX_ROOT}/cocos2dx/platform/third_party/android/prebuilt:${APP_ROOT}"

运行结果:

(8)现在来实现通过c++函数调用java层函数,显示一个对话框。在JniTestHelper.java添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
public  static  native  void  exitApp();
private  static  void  showTipDialog( final  String title,  final  String text)
{
     Message msg = mHandler.obtainMessage();
     msg.what = JniTest.SHOW_DIALOG;
     DialogMessage dm =  new  DialogMessage();
     dm.title = title;
     dm.msg = text;
     msg.obj = dm;
     msg.sendToTarget();
}

(9)创建一个DialogMessage.java,封装dialog要显示的数据。

1
2
3
4
5
6
7
8
9
10
11
/**
author:alexzhou
email :zhoujiangbohai@163.com
date  :2012-12-14
**/
public  class  DialogMessage {
     public  String title;
     public  String msg;
}

(10) 修改JniTest.java,添加显示对话框的函数:

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
28
29
30
31
32
33
public  static  final  int  SHOW_DIALOG =  0x0001 ;
    private  Handler mHandler =  new  Handler()
     {
         @Override
         public  void  handleMessage(Message msg) {
             switch (msg.what)
             {
             case  SHOW_DIALOG:
                 DialogMessage dm = (DialogMessage)msg.obj;
                 new  AlertDialog.Builder(JniTest. this )
                 .setTitle(dm.title)
                 .setMessage(dm.msg).setNegativeButton( "cancle" new DialogInterface.OnClickListener() {
                     @Override
                     public  void  onClick(DialogInterface dialog,  int  which) {
                         dialog.dismiss();
                     }
                 })
                 .setPositiveButton( "Ok" ,
                         new  DialogInterface.OnClickListener() {
                     @Override
                     public  void  onClick(DialogInterface dialog,  int  which) {
                         dialog.dismiss();
                         JniTestHelper.exitApp();
                     }
                 })
                 .create().show();
                 break ;
             }
         }
     };

(11)在test.h和test.cpp中添加显示对话框的接口:
test.h

1
2
3
4
extern  "C"
{
void  showTipDialog( const  char  *title,  const  char  *msg);
}

test.cpp

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
28
extern  "C"
{
void  showTipDialog( const  char  *title,  const  char  *msg)
{
     JniMethodInfo t;
     if (JniHelper::getStaticMethodInfo(t, CLASS_NAME,  "showTipDialog" "(Ljava/lang/String;Ljava/lang/String;)V" ))
     {
         jstring jTitle = t.env->NewStringUTF(title);
         jstring jMsg = t.env->NewStringUTF(msg);
         t.env->CallStaticVoidMethod(t.classID, t.methodID, jTitle, jMsg);
         t.env->DeleteLocalRef(jTitle);
         t.env->DeleteLocalRef(jMsg);
     }
}
void  Java_com_alexzhou_jni_JniTestHelper_setPackageName(JNIEnv *env, jobject thiz, jstring packageName)
{
     const  char  *pkgName = env->GetStringUTFChars(packageName, NULL);
     setPackageName(pkgName);
     env->ReleaseStringUTFChars(packageName, pkgName);
}
void  Java_com_alexzhou_jni_JniTestHelper_exitApp(JNIEnv *env, jobject thiz)
{
     exitApp();
}
}

(12) 修改Classes目录下的JniTest.h,添加代码:

1
2
3
4
void  exitApp()
{
     CCDirector::sharedDirector()->end();
}

(13)到此为止,所有代码都已经完成了,代码比较简单就不详细解释了,现在编译运行,效果如下:

源码下载地址:http://download.csdn.net/detail/zhoujianghai/4890792

转载请注明来自:Alex Zhou的程序世界,本文链接:http://codingnow.cn/cocos2d-x/992.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值