android中jni学习——jni的调用

JNI学习

JAVA以其跨平台的特性深受人们喜爱,而又正由于它的跨平台的目的,使得它和本地机器的各种内部联系变得很少,约束了它的功能。解决JAVA对本地操作的一种方法就是JNI。   JAVA通过JNI调用本地方法,而本地方法是以库文件的形式存放的(在WINDOWS平台上是DLL文件形式,在UNIX机器上是SO文件形式)。通过调用本地的库文件的内部方法,使JAVA可以实现和本地机器的紧密联系,调用系统级的各接口方法。

最近在公司里做了一个手机的项目,需要JAVA程序在发送短信的时候和第三方的短信服务器连接。短信接口是用C++写的。琢磨了三天,大致搞懂了JNI的主体部分。先将心得整理,希望各位朋友少走弯路。
首先引用一篇文章,介绍一个简单的JNI的调用的过程。
JAVA以其跨平台的特性深受人们喜爱,而又正由于它的跨平台的目的,使得它和本地机器的各种内部联系变得很少,约束了它的功能。解决JAVA对本地操作的一种方法就是JNI。
JAVA通过JNI调用本地方法,而本地方法是以库文件的形式存放的(在WINDOWS平台上是DLL文件形式,在UNIX机器上是SO文件形式)。通过调用本地的库文件的内部方法,使JAVA可以实现和本地机器的紧密联系,调用系统级的各接口方法。
简单介绍及应用如下: 
一、JAVA中所需要做的工作 
在JAVA程序中,首先需要在类中声明所调用的库名称,如下: 

[java] view plaincopy

1.  static {   

2.         System.loadLibrary(“goodluck”);   

3.  }  

 

在这里,库的扩展名字可以不用写出来,究竟是DLL还是SO,由系统自己判断。 
还需对将要调用的方法做本地声明,关键字为native。且只需要声明,而不需要具体实现。如下: 

[java] view plaincopy

1.  public native static void set(int i);   

2.      public native static int get();  

 


然后编译该JAVA程序文件,生成CLASS,再用JAVAH命令,JNI就会生成C/C++的头文件。 
例如程序testdll.java,内容为: 

[java] view plaincopy

1.  public class testdll   

2.  {   

3.  static   

4.  {   

5.  System.loadLibrary("goodluck");   

6.  }   

7.  public native static int get();   

8.  public native static void set(int i);   

9.  public static void main(String[] args)   

10. {   

11. testdll test = new testdll();   

12. test.set(10);   

13. System.out.println(test.get());   

14. }   

15. }  

 

 

用javac testdll.java编译它,会生成testdll.class。 
再用javah testdll,则会在当前目录下生成testdll.h文件,这个文件需要被C/C++程序调用来生成所需的库文件。 
二、C/C++中所需要做的工作 
对于已生成的.h头文件,C/C++所需要做的,就是把它的各个方法具体的实现。然后编译连接成库文件即可。再把库文件拷贝到JAVA程序的路径下面,就可以用JAVA调用C/C++所实现的功能了。 
接上例子。我们先看一下testdll.h文件的内容: 

[cpp] view plaincopy

1.  /* DO NOT EDIT THIS FILE - it is machine generated */   

2.  #include   

3.  /* Header for class testdll */   

4.  #ifndef _Included_testdll   

5.  #define _Included_testdll   

6.  #ifdef __cplusplus   

7.  extern "C" {   

8.  #endif   

9.  /*  

10. * Class: testdll  

11. * Method: get  

12. * Signature: ()I  

13. */   

14. JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass);   

15. /*  

16. * Class: testdll  

17. * Method: set  

18. * Signature: (I)V  

19. */   

20. JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint);   

21. #ifdef __cplusplus   

22. }   

23. #endif   

24. #endif  

 

在具体实现的时候,我们只关心两个函数原型 
JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass);
和 
JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint);
这里JNIEXPORT和JNICALL都是JNI的关键字,表示此函数是要被JNI调用的。而jint是以JNI为中介使JAVA的int类型与本地的int沟通的一种类型,我们可以视而不见,就当做int使用。函数的名称是JAVA_再加上java程序的package路径再加函数名组成的。参数中,我们也只需要关心在JAVA程序中存在的参数,至于JNIEnv*和jclass我们一般没有必要去碰它。 
好,下面我们用testdll.cpp文件具体实现这两个函数: 

[cpp] view plaincopy

1.  #include "testdll.h"   

2.  int i = 0;   

3.  JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass)   

4.  {   

5.  return i;   

6.  }   

7.  JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint j)   

8.  {   

9.  i = j;   

10. }  

 

编译连接成库文件,本例是在WINDOWS下做的,生成的是DLL文件。并且名称要与JAVA中需要调用的一致,这里就是goodluck.dll 。把goodluck.dll拷贝到testdll.class的目录下,java testdll运行它,就可以观察到结果了。

我的项目比较复杂,需要调用动态链接库,这样在JNI传送参数到C程序时,需要对参数进行处理转换。才可以被C程序识别。
大体程序如下:

[java] view plaincopy

1.  public class SendSMS {   

2.  static   

3.  {   

4.  System.out.println(System.getProperty("java.library.path"));   

5.  System.loadLibrary("sms");   

6.  }   

7.  public native static int SmsInit();   

8.  public native static int SmsSend(byte[] mobileNo, byte[] smContent);   

9.  }  

 


在这里要注意的是,path里一定要包含类库的路径,否则在程序运行时会抛出异常:

[java] view plaincopy

1.  java.lang.UnsatisfiedLinkError: no sms in java.library.path  

2.  at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1491)  

3.  at java.lang.Runtime.loadLibrary0(Runtime.java:788)  

4.  at java.lang.System.loadLibrary(System.java:834)  

5.  at com.mobilesoft.sms.mobilesoftinfo.SendSMS.(SendSMS.java:14)  

6.  at com.mobilesoft.sms.mobilesoftinfo.test.main(test.java:18)  

7.  Exception in thread "main"  



指引的路径应该到.dll文件的上一级,如果指到.dll,则会报:

[java] view plaincopy

1.  java.lang.UnsatisfiedLinkError: C:\sms.dll: Can't find dependent libraries  

2.  at java.lang.ClassLoader$NativeLibrary.load(Native Method)  

3.  at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1560)  

4.  at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1485)  

5.  at java.lang.Runtime.loadLibrary0(Runtime.java:788)  

6.  at java.lang.System.loadLibrary(System.java:834)  

7.  at com.mobilesoft.sms.mobilesoftinfo.test.main(test.java:18)  

8.  Exception in thread "main"  



通过编译,生成com_mobilesoft_sms_mobilesoftinfo_SendSMS.h头文件。(建议使用Jbuilder进行编译,操作比较简单!)这个头文件就是Java和C之间的纽带。要特别注意的是方法中传递的参数jbyteArray,这在接下来的过程中会重点介绍。

[cpp] view plaincopy

1.  /* DO NOT EDIT THIS FILE - it is machine generated */   

2.  #include   

3.  /* Header for class com_mobilesoft_sms_mobilesoftinfo_SendSMS */   

4.  #ifndef _Included_com_mobilesoft_sms_mobilesoftinfo_SendSMS   

5.  #define _Included_com_mobilesoft_sms_mobilesoftinfo_SendSMS   

6.  #ifdef __cplusplus   

7.  extern "C" {   

8.  #endif   

9.  /*  

10. * Class: com_mobilesoft_sms_mobilesoftinfo_SendSMS  

11. * Method: SmsInit  

12. * Signature: ()I  

13. */   

14. JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsInit   

15. (JNIEnv *, jclass);   

16. /*  

17. * Class: com_mobilesoft_sms_mobilesoftinfo_SendSMS  

18. * Method: SmsSend  

19. * Signature: ([B[B)I  

20. */   

21. JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsSend   

22. (JNIEnv *, jclass, jbyteArray, jbyteArray);   

23. #ifdef __cplusplus   

24. }   

25. #endif   

26. #endif  

 


对于我要调用的C程序的动态链接库,C程序也要提供一个头文件,sms.h。这个文件将要调用的方法罗列了出来。

[cpp] view plaincopy

1.  /*  

2.  * SMS API  

3.  * Author: yippit  

4.  * Date: 2004.6.8  

5.  */   

6.  #ifndef MCS_SMS_H   

7.  #define MCS_SMS_H   

8.  #define DLLEXPORT __declspec(dllexport)   

9.  /*sms storage*/   

10. #define SMS_SIM 0   

11. #define SMS_MT 1   

12. /*sms states*/   

13. #define SMS_UNREAD 0   

14. #define SMS_READ 1   

15. /*sms type*/   

16. #define SMS_NOPARSE -1   

17. #define SMS_NORMAL 0   

18. #define SMS_FLASH 1   

19. #define SMS_MMSNOTI 2   

20. typedef struct tagSmsEntry {   

21. int index; /*index, start from 1*/   

22. int status; /*read, unread*/   

23. int type; /*-1-can't parser 0-normal, 1-flash, 2-mms*/   

24. int storage; /*SMS_SIM, SMS_MT*/   

25. char date[24];   

26. char number[32];   

27. char text[144];   

28. } SmsEntry;   

29. DLLEXPORT int SmsInit(void);   

30. DLLEXPORT int SmsSend(char *phonenum, char *content);   

31. DLLEXPORT int SmsSetSCA(char *sca);   

32. DLLEXPORT int SmsGetSCA(char *sca);   

33. DLLEXPORT int SmsSetInd(int ind);   

34. DLLEXPORT int SmsGetInd(void);   

35. DLLEXPORT int SmsGetInfo(int storage, int *max, int *used);   

36. DLLEXPORT int SmsSaveFlash(int flag);   

37. DLLEXPORT int SmsRead(SmsEntry *entry, int storage, int index);   

38. DLLEXPORT int SmsDelete(int storage, int index);   

39. DLLEXPORT int SmsModifyStatus(int storage, int index); /*unread -> read*/   

40. #endif  

 

在有了这两个头文件之后,就可以进行C程序的编写了。也就是实现对JNI调用的两个方法。在网上的资料中,由于调用的方法实现的都比较简单,(大多是打印字符串等)所以避开了JNI中最麻烦的部分,也是最关键的部分,参数的传递。由于Java和C的编码是不同的,所以传递的参数是要进行再处理,否则C程序是会对参数在编译过程中提出警告,例如;warning C4024: 'SmsSend' : different types for formal and actualparameter 2等。

Sms.c的程序如下:

[cpp] view plaincopy

1.  #include "sms.h"   

2.  #include "com_mobilesoft_sms_mobilesoftinfo_SendSMS.h"   

3.  JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsInit(JNIEnv * env, jclass jobject)   

4.  {   

5.  return SmsInit();   

6.  }   

7.  JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsSend(JNIEnv * env, jclass jobject, jbyteArray mobileno, jbyteArray smscontent)   

8.  {   

9.  char * pSmscontent ;   

10. //jsize theArrayLengthJ = (*env)->GetArrayLength(env,mobileno);   

11. jbyte * arrayBody = (*env)->GetByteArrayElements(env,mobileno,0);   

12. char * pMobileNo = (char *)arrayBody;   

13. printf("[%s]\n ", pMobileNo);   

14. //jsize size = (*env)->GetArrayLength(env,smscontent);   

15. arrayBody = (*env)->GetByteArrayElements(env,smscontent,0);   

16. pSmscontent = (char *)arrayBody;   

17. printf("<%s>\n", pSmscontent);   

18. return SmsSend(pMobileNo,pSmscontent);   

19. }  

 


对于C或C++,在程序上是会有稍微的不同,这可以由读者对其进行适当的修改。这里要注意的是GetArrayLength,GetByteArrayElements等这些JNI中已经包含的方法,这些方法是专门对转换参数类型而提供的。具体的方法有很多,在下一篇中会做专门的介绍。
在完成了上述的文件后,可以对sms.c进行编译,生成.dll文件(建议在release中编译,这样动态链接库的容积会比较小!)
完成.dll文件的编译后,就可以在Java中调用C程序中的方法了。例如文件test.java

[java] view plaincopy

1.  public class test {   

2.  public test() {   

3.  }   

4.  public static void main(String[] args) {   

5.  byte[] mobileno = {   

6.  0x310x330x360x360x310x360x330x300x360x360x370x00};   

7.  String smscontentemp = "早上好";   

8.  byte[] temp = {0};   

9.  try {   

10. byte[] smscontentdb = smscontentemp.getBytes("gbk");   

11. byte[] smscontent = new byte[smscontentdb.length + temp.length];   

12. System.arraycopy(smscontentdb, 0, smscontent, 0, smscontentdb.length);   

13. System.arraycopy(temp, 0, smscontent, smscontentdb.length, temp.length);   

14. SendSMS sendSMS = new SendSMS();   

15. sendSMS.SmsInit();   

16. if (sendSMS.SmsSend(mobileno, smscontent) >= 0) {   

17. System.out.println("chenggong !");   

18. }   

19. else {   

20. System.out.println("shibai !");   

21. }   

22. }catch (Exception ex) {}   

23. }   

24. }  

 

在这个文件中要注意的有一点,就是在传递字节数组到C程序中时,最后的结尾一定要以0结束。这是一个偷懒的做法,不过是个有效的做法。因为大多数情况下,接口是由第三方提供的。所以我们一般是不知道在C的方法里,具体是怎么处理参数的。而C又是要求数组是有长度。所以,在Java中,如果你不想写程序传数组的长度,那么在数组中以0结尾就是最方便的方法了。当然,如果有更好的方法也希望大家提出。
到这里,一个完整的Java通过JNI调用动态链接库的程序就完成了。实际上也不是很复杂。只要多注意一下细节,是很容易得出来的。

 

 

 

 

Android NDK开发之Jni调用Java对象

本地代码中使用Java对象

通过使用合适的JNI函数,你可以创建Java对象,get、set 静态(static)和 实例(instance)的域,调用静态(static)和实例(instance)函数。JNI通过ID识别域和方法,一个域或方法的ID是任何处理域和方法的函数的必须参数。
下表列出了用以得到静态(static)和实例(instance)的域与方法的JNI函数。每个函数接受(作为参数)域或方法的类,它们的名称,符号和它们对应返回的jfieldID或jmethodID。

函数 

描述

GetFieldID

 得到一个实例的域的ID

GetStaticFieldID 

得到一个静态的域的ID

GetMethodID

得到一个实例的方法的ID

GetStaticMethodID

得到一个静态方法的ID

构造一个Java对象的实例

C代码  

1.  jclass cls = (*env)->FindClass(env, "Lpackagename/classname;");  //创建一个class的引用  

2.  jmethodID id = (*env)->GetMethodID(env, cls, """(D)V");  //注意这里方法的名称是"",它表示这是一个构造函数,而且构造参数是double型的  

3.  jobject obj = (*env)->NewObjectA(env, cls, id, args);  //获得一实例,args是构造函数的参数,它是一个jvalue*类型。  

[c] view plaincopy

1.  jclass cls = (*env)->FindClass(env, "Lpackagename/classname;");  //创建一个class的引用  

2.  jmethodID id = (*env)->GetMethodID(env, cls, """(D)V");  //注意这里方法的名称是"",它表示这是一个构造函数,而且构造参数是double型的  

3.  jobject obj = (*env)->NewObjectA(env, cls, id, args);  //获得一实例,args是构造函数的参数,它是一个jvalue*类型。  

首先是获得一个Java类的class引用 (*env)->FindClass(env,"Lpackagename/classname;");  请注意参数:Lpackagename/classname; L代表这是在描述一个对象类型,packagename/classname是该对象耳朵class路径,请注意一定要以分号(;)结束!

然后是获取函数的idjmethodIDid = env->GetMethodID(cls, "", "(D)V");  第一个是刚刚获得的class引用,第二个是方法的名称,最后一个就是方法的签名

还是不懂?我曾经如此,请接着看...

 

难理解的函数签名

JNINativeMethod的定义如下:

C代码  

1.  typedef struct {  

2.     const char* name;  

3.     const char* signature;  

4.     void* fnPtr;  

5.  } JNINativeMethod;  

[c] view plaincopy

1.  typedef struct {  

2.     const char* name;  

3.     const char* signature;  

4.     void* fnPtr;  

5.  } JNINativeMethod;  

第一个变量nameJava中函数的名字。 
第二个变量signature,用字符串是描述了函数的参数和返回值 
第三个变量fnPtr是函数指针,指向C函数。

 

其中比较难以理解的是第二个参数,例如
"()V"
"(II)V"
"(Ljava/lang/String;Ljava/lang/String;)V"


实际上这些字符是与函数的参数类型一一对应的。
"()"
中的字符表示参数,后面的则代表返回值。例如"()V"就表示voidFunc();
"(II)V"
表示void Func(int, int);

 

那其他情况呢?请查看下表:

类型

符号

boolean

Z

byte

B

char

C

short

S

int

I

long

L

float

F

double

D

void

V

object对象

LClassName;      L类名;

Arrays

[array-type        [数组类型

methods方法

(argument-types)return-type     (参数类型)返回类型

稍稍补充一下:

1、方法参数或者返回值为java中的对象时,签名中必须以“L”加上其路径,不过此路径必须以“/”分开,自定义的对象也使用本规则

比如说 java.lang.String“java/lang/String”com.nedu.jni.helloword.Student"Lcom/nedu/jni/helloword/Student;"

2、方法参数或者返回值为数组类型时,请前加上[

例如[I表示 int[],[[[D表示double[][][],即几维数组就加几个[

在本地方法中调用Java对象的方法

1、获取你需要访问的Java对象的类:

C代码  

1.  jclass cls = (*env)->GetObjectClass(env, obj);       // 使用GetObjectClass方法获取obj对应的jclass   

2.  jclass cls = (*env)->FindClass(“android/util/log”) // 直接搜索类名,需要是static修饰的类。  

[c] view plaincopy

1.  jclass cls = (*env)->GetObjectClass(env, obj);       // 使用GetObjectClass方法获取obj对应的jclass   

2.  jclass cls = (*env)->FindClass(“android/util/log”) // 直接搜索类名,需要是static修饰的类。  

 
2、获取MethodID

C代码  

1.  jmethodID mid = (*env)->GetMethodID(env, cls, "callback""(I)V"); //GetStaticMethodID(…),获取静态方法的ID使用GetMethdoID方法获取你要使用的方法的MethdoID  

[c] view plaincopy

1.  jmethodID mid = (*env)->GetMethodID(env, cls, "callback""(I)V"); //GetStaticMethodID(…),获取静态方法的ID使用GetMethdoID方法获取你要使用的方法的MethdoID  

其参数的意义: 
env-->JNIEnv 

cls-->第一步获取的jclass 
"callback"-->要调用的方法名 
"(I)V"-->
方法的Signature,签名同前面的JNI规则。

 

3、调用方法:

C代码  

1.  (*env)->CallVoidMethod(env, obj, mid, depth);// CallStaticIntMethod(….)  调用静态方法  

[c] view plaincopy

1.  (*env)->CallVoidMethod(env, obj, mid, depth);// CallStaticIntMethod(….)  调用静态方法  

使用CallVoidMethod方法调用方法。参数的意义: 
env-->JNIEnv 

obj-->通过本地方法穿过来的jobject 
mid-->要调用的MethodID(即第二步获得的MethodID 
depth-->
方法需要的参数(对应方法的需求,添加相应的参数)

 

注:这里使用的是CallVoidMethod方法调用,因为没有返回值,如果有返回值的话使用对应的方法,在后面会提到。

C代码  

1.  CallVoidMethod                   CallStaticVoidMethod  

2.  CallIntMethod                     CallStaticVoidMethod  

3.  CallBooleanMethod              CallStaticVoidMethod  

4.  CallByteMethod                   CallStaticVoidMethod  

[c] view plaincopy

1.  CallVoidMethod                   CallStaticVoidMethod  

2.  CallIntMethod                     CallStaticVoidMethod  

3.  CallBooleanMethod              CallStaticVoidMethod  

4.  CallByteMethod                   CallStaticVoidMethod  

现在稍稍明白文章开始构造Java对象那个实例了吧?让我们继续深入一下:

 

Jni操作JavaString对象

java程序中传过去的String对象在本地方法中对应的是jstring类型,jstring类型和c中的char*不同,所以如果你直接当做char*使用的话,就会出错。因此在使用之前需要将jstring转换成为c/c++中的char*,这里使用JNIEnv提供的方法转换。

const char *str = (*env)->GetStringUTFChars(env, jstr, 0);
(*env)->ReleaseStringUTFChars(env, jstr, str);

这里使用GetStringUTFChars方法将传进来的promptjstring类型)转换成为UTF8的格式,就能够在本地方法中使用了。
注意:在使用完你所转换之后的对象之后,需要显示调用ReleaseStringUTFChars方法,让JVM释放转换成UTF-8string的对象的空间,如果不显示的调用的话,JVM中会一直保存该对象,不会被垃圾回收器回收,因此就会导致内存溢出。

下面是Jni访问String对象的一些方法:

  • GetStringUTFChars          jstring转换成为UTF-8格式的char*
  • GetStringChars               jstring转换成为Unicode格式的char*
  • ReleaseStringUTFChars    释放指向UTF-8格式的char*的指针
  • ReleaseStringChars         释放指向Unicode格式的char*的指针
  • NewStringUTF               创建一个UTF-8格式的String对象
  • NewString                    创建一个Unicode格式的String对象
  • GetStringUTFLength      获取UTF-8格式的char*的长度
  • GetStringLength           获取Unicode格式的char*的长度

下面提供两个String对象和char*互转的方法:

C代码  

1.  /* c/c++ string turn to java jstring */  

2.  jstring charToJstring(JNIEnv* env, const char* pat)  

3.  {  

4.      jclass     strClass = (*env)->FindClass(env, "java/lang/String");  

5.      jmethodID  ctorID   = (*env)->GetMethodID(env, strClass, """([BLjava/lang/String;)V");  

6.      jbyteArray bytes    = (*env)->NewByteArray(env, strlen(pat));  

7.      (*env)->SetByteArrayRegion(env, bytes, 0, strlen(pat), (jbyte*)pat);  

8.      jstring    encoding = (*env)->NewStringUTF(env, "UTF-8");  

9.      return (jstring)(*env)->NewObject(env, strClass, ctorID, bytes, encoding);  

10. }  

11.   

12. /* java jstring turn to c/c++ char* */  

13. char* jstringToChar(JNIEnv* env, jstring jstr)  

14. {         

15.     char* pStr = NULL;  

16.     jclass     jstrObj   = (*env)->FindClass(env, "java/lang/String");  

17.     jstring    encode    = (*env)->NewStringUTF(env, "utf-8");  

18.     jmethodID  methodId  = (*env)->GetMethodID(env, jstrObj, "getBytes""(Ljava/lang/String;)[B");  

19.     jbyteArray byteArray = (jbyteArray)(*env)->CallObjectMethod(env, jstr, methodId, encode);  

20.     jsize      strLen    = (*env)->GetArrayLength(env, byteArray);  

21.     jbyte      *jBuf     = (*env)->GetByteArrayElements(env, byteArray, JNI_FALSE);  

22.     if (jBuf > 0)  

23.     {  

24.         pStr = (char*)malloc(strLen + 1);  

25.         if (!pStr)  

26.         {  

27.             return NULL;  

28.         }  

29.         memcpy(pStr, jBuf, strLen);  

30.         pStr[strLen] = 0;  

31.     }  

32.     env->ReleaseByteArrayElements(byteArray, jBuf, 0);  

33.     return pStr;  

34. }  

 

 

 

   

Android Jni调用浅述

注明出处! http://blog.csdn.net/flydream0/article/details/7371692

1 简述

   JNI是JavaNative Interface的缩写,中文为JAVA本地调用。从Java1.1开始,JavaNative Interface(JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。

   由于Android的应用层的类都是以Java写的,这些Java类编译为Dex型式的字节码之后,必须依靠Dalvik虚拟机来运行,在Android中Dalvik虚拟机扮演很重要的角色.而Android中间件是由C/C++写的,这些C/C++写的组件并不是在Dalvik虚拟机上运行的。那么应用层上的Java代码又是如何与C/C++写的组件之间又是如何沟通的?

2 载入.so文件

System.loadLibrary(*.so文件);

在java代码中,可以通过loadLibrary要求VM装载so文件,java代码一般如下形式:

[java] view plaincopy

1.  public class jnitest {  

2.      static {  

3.          System.loadLibrary("jnitest");  

4.      }  

5.      //...  

6.  }  

上述代码运行时将会在/system/lib/目录下查找libjnitest.so文件,将载入VM,这样,java代码和C组件之间就构成了联系,接下来就可以通过一些方法可以相互调用了.

3 JNI_OnLoad与JNI_OnUnload

   在Android中,当程序在java层运行System.loadLibrary("jnitest");这行代码后,程序会去载入libjnitest.so文件,与此同时,产生一个"Load"事件,这个事件触发后,程序默认会在载入的.so文件的函数列表中查找JNI_OnLoad函数并执行,与"Load"事件相对,当载入的.so文件被卸载时,“Unload”事件被触发,此时,程序默认会去在载入的.so文件的函数列表中查找JNI_OnUnload函数并执行,然后卸载.so文件。需要注意的是,JNI_OnLoad与JNI_OnUnload这两个函数在.so组件中并不是强制要求的,用户也可以不去实现,java代码一样可以调用到C组件中的函数,在接下来的章节中会讲到这点.

   之所以在C组件中去实现这两个函数(特别是JNI_OnLoad函数),往往是做一个初始化工作或“善后”工作。可以这样认为,将JNI_ONLoad看成是.so组件的初始化函数,当其第一次被装载时被执行(window下的dll文件也可类似的机制,在_DLL_Main()函数中,通过一个swithcase语句来识别当前是载入还是卸载)。将JNI_OnUnload函数看成是析构函数,当其被卸载时被调用。

   由此看来,就不难明白为什么很多jniC组件中会实现JNI_OnLoad这个函数了。 一般情况下,在C组件中的JNI_OnLoad函数用来实现给VM注册接口,以方便VM可以快速的找到Java代码需要调用的C函数。(此外,JNI_OnLoad函数还有另外一个功能,那就是告诉VM此C组件使用那一个JNI版本,如果未实现JNI_OnLoad函数,则默认是JNI 1.1版本)。

4 显式注册native方法

4.1 显式注册的作用:

   应用层的Java类别通过VM而调用到native函数。一般是通过VM去寻找*.so里的native函数。如果需要连续呼叫很多次,每次都需要寻找一遍,会多花许多时间。此时,C组件开发者可以将本地函数向VM进行注册,以便能加快后续调用native函数的效率.可以这么想象一下,假设VM内部一个native函数链表,初始时是空的,在未显式注册之前此native函数链表是空的,每次java调用native函数之前会首先在此链表中查找需要查找需要调用的native函数,如果找到就直接使用,如果未找到,得再通过载入的.so文件中的函数列表中去查找,且每次java调用native函数都是进行这样的流程,因此,效率就自然会下降,为了克服这样现象,我们可以通过在.so文件载入初始化时,即JNI_OnLoad函数中,先行将native函数注册到VM的native函数链表中去,这样一来,后续每次java调用native函数时都会在VM中的native函数链表中找到对应的函数,从而加快速度.

注:Android 源码开发环境下,大多采用显式注册native方法.

4.2 在Android源码开发模式下有两种方法可以实现显示注册native方法:

方法一: 使用JNIHelp.h头文件中定义的jniRegisterNativeMethods来实现.

如~/WORKING_DIRECTORY/frameworks/base/services/jni/com_android_server_location_GpsLocationProvider.cpp:

注:此文件同级目录中的其它cpp文件大多采用此种方法进行native方法显式注册.

[cpp] view plaincopy

1.  static JNINativeMethod sMethods[] = {  

2.       /* name, signature, funcPtr */  

3.      {"class_init_native""()V", (void *)android_location_GpsLocationProvider_class_init_native},  

4.      {"native_is_supported""()Z", (void*)android_location_GpsLocationProvider_is_supported},  

5.      {"native_init""()Z", (void*)android_location_GpsLocationProvider_init},  

6.      {"native_cleanup""()V", (void*)android_location_GpsLocationProvider_cleanup},  

7.      {"native_set_position_mode""(IIIII)Z", (void*)android_location_GpsLocationProvider_set_position_mode},  

8.      {"native_start""()Z", (void*)android_location_GpsLocationProvider_start},  

9.      {"native_stop""()Z", (void*)android_location_GpsLocationProvider_stop},  

10.     {"native_delete_aiding_data""(I)V", (void*)android_location_GpsLocationProvider_delete_aiding_data},  

11.     {"native_read_sv_status""([I[F[F[F[I)I", (void*)android_location_GpsLocationProvider_read_sv_status},  

12.     {"native_read_nmea""([BI)I", (void*)android_location_GpsLocationProvider_read_nmea},  

13.     {"native_inject_time""(JJI)V", (void*)android_location_GpsLocationProvider_inject_time},  

14.     {"native_inject_location""(DDF)V", (void*)android_location_GpsLocationProvider_inject_location},  

15.     {"native_supports_xtra""()Z", (void*)android_location_GpsLocationProvider_supports_xtra},  

16.     {"native_inject_xtra_data""([BI)V", (void*)android_location_GpsLocationProvider_inject_xtra_data},  

17.     {"native_agps_data_conn_open""(Ljava/lang/String;)V", (void*)android_location_GpsLocationProvider_agps_data_conn_open},  

18.     {"native_agps_data_conn_closed""()V", (void*)android_location_GpsLocationProvider_agps_data_conn_closed},  

19.     {"native_agps_data_conn_failed""()V", (void*)android_location_GpsLocationProvider_agps_data_conn_failed},  

20.     {"native_agps_set_id","(ILjava/lang/String;)V",(void*)android_location_GpsLocationProvider_agps_set_id},  

21.     {"native_agps_set_ref_location_cellid","(IIIII)V",(void*)android_location_GpsLocationProvider_agps_set_reference_location_cellid},  

22.     {"native_set_agps_server""(ILjava/lang/String;I)V", (void*)android_location_GpsLocationProvider_set_agps_server},  

23.     {"native_send_ni_response""(II)V", (void*)android_location_GpsLocationProvider_send_ni_response},  

24.     {"native_agps_ni_message""([BI)V", (void *)android_location_GpsLocationProvider_agps_send_ni_message},  

25.     {"native_get_internal_state""()Ljava/lang/String;", (void*)android_location_GpsLocationProvider_get_internal_state},  

26.     {"native_update_network_state""(ZIZZLjava/lang/String;Ljava/lang/String;)V", (void*)android_location_GpsLocationProvider_update_network_state },  

27. };  

28.   

29. int register_android_server_location_GpsLocationProvider(JNIEnv* env)  

30. {  

31.     return jniRegisterNativeMethods(env, "com/android/server/location/GpsLocationProvider", sMethods, NELEM(sMethods));  

32. }  

其中jniRegisterNativeMethods和NELEM都是在头文件JNIHelp.h定义的,得:

[plain] view plaincopy

1.  #include "JNIHelp.h"  

在Android.mk文件中得加上:

[plain] view plaincopy

1.  LOCAL_SHARED_LIBRARIES +=libnativehelper   

 

方法二:使用AndroidRuntime::registerNativeMethods

如~/WORKING_DIRECTORY/frameworks/base/media/jni/android_media_MediaPlayer.cpp:

注:当前目录下其它cpp文件大多采用此种方法进行显式native注册.

[cpp] view plaincopy

1.  static JNINativeMethod gMethods[] = {  

2.      {"setDataSource",       "(Ljava/lang/String;)V",            (void *)android_media_MediaPlayer_setDataSource},  

3.    

4.      {  

5.          "_setDataSource",  

6.          "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",  

7.          (void *)android_media_MediaPlayer_setDataSourceAndHeaders  

8.      },  

9.    

10.     {"setDataSource",       "(Ljava/io/FileDescriptor;JJ)V",    (void *)android_media_MediaPlayer_setDataSourceFD},  

11.     {"_setVideoSurface",    "(Landroid/view/Surface;)V",        (void *)android_media_MediaPlayer_setVideoSurface},  

12.     {"prepare",             "()V",                              (void *)android_media_MediaPlayer_prepare},  

13.     {"prepareAsync",        "()V",                              (void *)android_media_MediaPlayer_prepareAsync},  

14.     {"_start",              "()V",                              (void *)android_media_MediaPlayer_start},  

15.     {"_stop",               "()V",                              (void *)android_media_MediaPlayer_stop},  

16.     {"getVideoWidth",       "()I",                              (void *)android_media_MediaPlayer_getVideoWidth},  

17.     {"getVideoHeight",      "()I",                              (void *)android_media_MediaPlayer_getVideoHeight},  

18.     {"seekTo",              "(I)V",                             (void *)android_media_MediaPlayer_seekTo},  

19.     //...  

20. }  

21. // This function only registers the native methods  

22. static int register_android_media_MediaPlayer(JNIEnv *env)  

23. {  

24.     return AndroidRuntime::registerNativeMethods(env,  

25.                 "android/media/MediaPlayer", gMethods, NELEM(gMethods));  

26. }  

其中AndroidRuntime::registerNativeMethods是在头文件android_runtime/AndroidRuntime.h中定义,使用时得:

[cpp] view plaincopy

1.  #include "android_runtime/AndroidRuntime.h"  

且得在Android.mk文件中加上:

[plain] view plaincopy

1.  LOCAL_SHARED_LIBRARIES += \  

2.      libandroid_runtime   


有关函数命名,请参考博客内另一篇文章:Android下如何通过JNI方法向上提供接口总结

注:以上两种显式注册native方法都只是适用于Android源码开发环境下,至于在Android NDK环境下如何显式注册native方法,暂时还没有研究过,且此两种方法目前NDK还不支持,NDK开发模式下一般采用隐式注册native函数,即接下来要讲的内容.

 

5 隐式注册native方法

前面第3节已经讲到,JNI_OnLoad和JNI_OnUnload函数并不是强制要求实现的,在这种情况下,就相当于在载入.so文件时,没有了初始化函数,既然没有了初始化函数,那显式注册native方法也行不通了。那这个时候又该如何让应用层的java代码调用下层的C函数呢?

   幸运地是,即使我们不使用任何代码做native函数显式注册,应用层的java代码在调用native函数时,也会采用默认的方法到转入的.so文件中的函数列表中查找对应的native 函数,只不过,这个native函数与java类型中声明的native函数的名字之前有一种默认的对应关系。如:

java类的native成员函数:publicnative int socket_send(int cmdid,String argus);默认会在.so文件中的函数列表中查找jint JNICALLJava_com_hase_bclm_bclm_socket_1send(JNIEnv *env, jobject obj, jint cmdid,jstring argus);函数,一旦找到此对应的函数,VM就会将此native函数自动注册到VM内部的native函数链表中,以便加快后续相同jni调用.

可接下来的问题是,作为码农的我们,又是如何知道javanative成员函数对应着C组件中的native 函数的名字呢?简单地函数名字也许我们能搞定,复杂一点的就要傻眼了。同样幸运地是,JDK提供了一个javah工具,可以用这个工具来自动通过.class文件来生成C组件的头文件.

比如你用java写了一个xxx.java文件,里边的java类型里面声明了一些native成员函数,此xxx.java文件编译后会生成xxx.class文件,那么就在你生成的xxx.class包所在目录输入命令行:

[plain] view plaincopy

1.  $javah -jni com.packagename.yourclassname  

就会在当前目录下生成一个头文件。

比如:

javah-jni com.test.example

则在当前目录下生成com_test_example.h头文件.

当前目录下的com结构为:com/test/example.class

再将此头文件拷贝到你的C组件工程内,实现其中声明的native函数即可.

 

之前在显式注册native函数的相关章节中已经说明,显式注册是将native函数添加到VM内部的native函数链表中,以加快后续jni调用的效率,其实在隐性native 注册时,每一次执行某个jni调用时,VM在.so函数列表中找到对应的native函数后,同样也会将其注册到VM内部的native函数链表中,由此看到,隐式native注册的方法除了第一次执某个jni调用时会稍微速度慢点外,后续同样的调用就会直接在VM内部的native函数链表中找到对应的native函数,这样看来,显式与隐式注册native方法,其实效率相差无几.

 

OK,到此结束!

 

 

 

JNI完全手册

分类: JNI2013-01-0921:58 106人阅读 评论(0) 收藏 举报

JAVA以其跨平台的特性深受人们喜爱,而又正由于它的跨平台的目的,使得它和本地机器的各种内部联系变得很少,约束了它的功能。解决JAVA对本地操作的一种方法就是JNI。  JAVA通过JNI调用本地方法,而本地方法是以库文件的形式存放的(在WINDOWS平台上是DLL文件形式,在UNIX机器上是SO文件形式)。通过调用本地的库文件的内部方法,使JAVA可以实现和本地机器的紧密联系,调用系统级的各接口方法。

最近在公司里做了一个手机的项目,需要JAVA程序在发送短信的时候和第三方的短信服务器连接。短信接口是用C++写的。琢磨了三天,大致搞懂了JNI的主体部分。先将心得整理,希望各位朋友少走弯路。
首先引用一篇文章,介绍一个简单的JNI的调用的过程。
JAVA以其跨平台的特性深受人们喜爱,而又正由于它的跨平台的目的,使得它和本地机器的各种内部联系变得很少,约束了它的功能。解决JAVA对本地操作的一种方法就是JNI。
JAVA通过JNI调用本地方法,而本地方法是以库文件的形式存放的(在WINDOWS平台上是DLL文件形式,在UNIX机器上是SO文件形式)。通过调用本地的库文件的内部方法,使JAVA可以实现和本地机器的紧密联系,调用系统级的各接口方法。
简单介绍及应用如下: 
一、JAVA中所需要做的工作 
在JAVA程序中,首先需要在类中声明所调用的库名称,如下: 

static { 
System.loadLibrary(“goodluck”); 
}


在这里,库的扩展名字可以不用写出来,究竟是DLL还是SO,由系统自己判断。 
还需对将要调用的方法做本地声明,关键字为native。且只需要声明,而不需要具体实现。如下: 

public native static void set(int i); 
public native static int get();


然后编译该JAVA程序文件,生成CLASS,再用JAVAH命令,JNI就会生成C/C++的头文件。 
例如程序testdll.java,内容为: 

[java] view plaincopy

1.  public class testdll   

2.  {   

3.  static   

4.  {   

5.  System.loadLibrary("goodluck");   

6.  }   

7.  public native static int get();   

8.  public native static void set(int i);   

9.  public static void main(String[] args)   

10. {   

11. testdll test = new testdll();   

12. test.set(10);   

13. System.out.println(test.get());   

14. }   

15. }  


用javac testdll.java编译它,会生成testdll.class。 
再用javah testdll,则会在当前目录下生成testdll.h文件,这个文件需要被C/C++程序调用来生成所需的库文件。 
二、C/C++中所需要做的工作 
对于已生成的.h头文件,C/C++所需要做的,就是把它的各个方法具体的实现。然后编译连接成库文件即可。再把库文件拷贝到JAVA程序的路径下面,就可以用JAVA调用C/C++所实现的功能了。 
接上例子。我们先看一下testdll.h文件的内容: 

[cpp] view plaincopy

1.  /* DO NOT EDIT THIS FILE - it is machine generated */   

2.  #include   

3.  /* Header for class testdll */   

4.  #ifndef _Included_testdll   

5.  #define _Included_testdll   

6.  #ifdef __cplusplus   

7.  extern "C" {   

8.  #endif   

9.  /*  

10. * Class: testdll  

11. * Method: get  

12. * Signature: ()I  

13. */   

14. JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass);   

15. /*  

16. * Class: testdll  

17. * Method: set  

18. * Signature: (I)V  

19. */   

20. JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint);   

21. #ifdef __cplusplus   

22. }   

23. #endif   

24. #endif  


在具体实现的时候,我们只关心两个函数原型 
JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass); 和 
JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint);
这里JNIEXPORT和JNICALL都是JNI的关键字,表示此函数是要被JNI调用的。而jint是以JNI为中介使JAVA的int类型与本地的int沟通的一种类型,我们可以视而不见,就当做int使用。函数的名称是JAVA_再加上java程序的package路径再加函数名组成的。参数中,我们也只需要关心在JAVA程序中存在的参数,至于JNIEnv*和jclass我们一般没有必要去碰它。 
好,下面我们用testdll.cpp文件具体实现这两个函数: 

[cpp] view plaincopy

1.  #include "testdll.h"   

2.  int i = 0;   

3.  JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass)   

4.  {   

5.  return i;   

6.  }   

7.  JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint j)   

8.  {   

9.  i = j;   

10. }  

 

编译连接成库文件,本例是在WINDOWS下做的,生成的是DLL文件。并且名称要与JAVA中需要调用的一致,这里就是goodluck.dll 。把goodluck.dll拷贝到testdll.class的目录下,java testdll运行它,就可以观察到结果了。

我的项目比较复杂,需要调用动态链接库,这样在JNI传送参数到C程序时,需要对参数进行处理转换。才可以被C程序识别。
大体程序如下:

[java] view plaincopy

1.  public class SendSMS {   

2.  static   

3.  {   

4.  System.out.println(System.getProperty("java.library.path"));   

5.  System.loadLibrary("sms");   

6.  }   

7.  public native static int SmsInit();   

8.  public native static int SmsSend(byte[] mobileNo, byte[] smContent);   

9.  }  


在这里要注意的是,path里一定要包含类库的路径,否则在程序运行时会抛出异常:
java.lang.UnsatisfiedLinkError: no sms in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1491)
at java.lang.Runtime.loadLibrary0(Runtime.java:788)
at java.lang.System.loadLibrary(System.java:834)
at com.mobilesoft.sms.mobilesoftinfo.SendSMS.(SendSMS.java:14)
at com.mobilesoft.sms.mobilesoftinfo.test.main(test.java:18)
Exception in thread "main"
指引的路径应该到.dll文件的上一级,如果指到.dll,则会报:
java.lang.UnsatisfiedLinkError: C:\sms.dll: Can't find dependent libraries
at java.lang.ClassLoader$NativeLibrary.load(Native Method)
at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1560)
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1485)
at java.lang.Runtime.loadLibrary0(Runtime.java:788)
at java.lang.System.loadLibrary(System.java:834)
at com.mobilesoft.sms.mobilesoftinfo.test.main(test.java:18)
Exception in thread "main"

通过编译,生成com_mobilesoft_sms_mobilesoftinfo_SendSMS.h头文件。(建议使用Jbuilder进行编译,操作比较简单!)这个头文件就是Java和C之间的纽带。要特别注意的是方法中传递的参数jbyteArray,这在接下来的过程中会重点介绍。

[cpp] view plaincopy

1.  /* DO NOT EDIT THIS FILE - it is machine generated */   

2.  #include   

3.  /* Header for class com_mobilesoft_sms_mobilesoftinfo_SendSMS */   

4.  #ifndef _Included_com_mobilesoft_sms_mobilesoftinfo_SendSMS   

5.  #define _Included_com_mobilesoft_sms_mobilesoftinfo_SendSMS   

6.  #ifdef __cplusplus   

7.  extern "C" {   

8.  #endif   

9.  /*  

10. * Class: com_mobilesoft_sms_mobilesoftinfo_SendSMS  

11. * Method: SmsInit  

12. * Signature: ()I  

13. */   

14. JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsInit   

15. (JNIEnv *, jclass);   

16. /*  

17. * Class: com_mobilesoft_sms_mobilesoftinfo_SendSMS  

18. * Method: SmsSend  

19. * Signature: ([B[B)I  

20. */   

21. JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsSend   

22. (JNIEnv *, jclass, jbyteArray, jbyteArray);   

23. #ifdef __cplusplus   

24. }   

25. #endif   

26. #endif  


对于我要调用的C程序的动态链接库,C程序也要提供一个头文件,sms.h。这个文件将要调用的方法罗列了出来。

[cpp] view plaincopy

1.  /*  

2.  * SMS API  

3.  * Author: yippit  

4.  * Date: 2004.6.8  

5.  */   

6.  #ifndef MCS_SMS_H   

7.  #define MCS_SMS_H   

8.  #define DLLEXPORT __declspec(dllexport)   

9.  /*sms storage*/   

10. #define SMS_SIM 0   

11. #define SMS_MT 1   

12. /*sms states*/   

13. #define SMS_UNREAD 0   

14. #define SMS_READ 1   

15. /*sms type*/   

16. #define SMS_NOPARSE -1   

17. #define SMS_NORMAL 0   

18. #define SMS_FLASH 1   

19. #define SMS_MMSNOTI 2   

20. typedef struct tagSmsEntry {   

21. int index; /*index, start from 1*/   

22. int status; /*read, unread*/   

23. int type; /*-1-can't parser 0-normal, 1-flash, 2-mms*/   

24. int storage; /*SMS_SIM, SMS_MT*/   

25. char date[24];   

26. char number[32];   

27. char text[144];   

28. } SmsEntry;   

29. DLLEXPORT int SmsInit(void);   

30. DLLEXPORT int SmsSend(char *phonenum, char *content);   

31. DLLEXPORT int SmsSetSCA(char *sca);   

32. DLLEXPORT int SmsGetSCA(char *sca);   

33. DLLEXPORT int SmsSetInd(int ind);   

34. DLLEXPORT int SmsGetInd(void);   

35. DLLEXPORT int SmsGetInfo(int storage, int *max, int *used);   

36. DLLEXPORT int SmsSaveFlash(int flag);   

37. DLLEXPORT int SmsRead(SmsEntry *entry, int storage, int index);   

38. DLLEXPORT int SmsDelete(int storage, int index);   

39. DLLEXPORT int SmsModifyStatus(int storage, int index); /*unread -> read*/   

40. #endif  

 

在有了这两个头文件之后,就可以进行C程序的编写了。也就是实现对JNI调用的两个方法。在网上的资料中,由于调用的方法实现的都比较简单,(大多是打印字符串等)所以避开了JNI中最麻烦的部分,也是最关键的部分,参数的传递。由于Java和C的编码是不同的,所以传递的参数是要进行再处理,否则C程序是会对参数在编译过程中提出警告,例如;warning C4024: 'SmsSend': different types for formal and actual parameter 2等。

Sms.c的程序如下:

[cpp] view plaincopy

1.  #include "sms.h"   

2.  #include "com_mobilesoft_sms_mobilesoftinfo_SendSMS.h"   

3.  JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsInit(JNIEnv * env, jclass jobject)   

4.  {   

5.  return SmsInit();   

6.  }   

7.  JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsSend(JNIEnv * env, jclass jobject, jbyteArray mobileno, jbyteArray smscontent)   

8.  {   

9.  char * pSmscontent ;   

10. //jsize theArrayLengthJ = (*env)->GetArrayLength(env,mobileno);   

11. jbyte * arrayBody = (*env)->GetByteArrayElements(env,mobileno,0);   

12. char * pMobileNo = (char *)arrayBody;   

13. printf("[%s]\n ", pMobileNo);   

14. //jsize size = (*env)->GetArrayLength(env,smscontent);   

15. arrayBody = (*env)->GetByteArrayElements(env,smscontent,0);   

16. pSmscontent = (char *)arrayBody;   

17. printf("<%s>\n", pSmscontent);   

18. return SmsSend(pMobileNo,pSmscontent);   

19. }  


对于C或C++,在程序上是会有稍微的不同,这可以由读者对其进行适当的修改。这里要注意的是GetArrayLength,GetByteArrayElements等这些JNI中已经包含的方法,这些方法是专门对转换参数类型而提供的。具体的方法有很多,在下一篇中会做专门的介绍。
在完成了上述的文件后,可以对sms.c进行编译,生成.dll文件(建议在release中编译,这样动态链接库的容积会比较小!)
完成.dll文件的编译后,就可以在Java中调用C程序中的方法了。例如文件test.java

[java] view plaincopy

1.  public class test {   

2.  public test() {   

3.  }   

4.  public static void main(String[] args) {   

5.  byte[] mobileno = {   

6.  0x310x330x360x360x310x360x330x300x360x360x370x00};   

7.  String smscontentemp = "早上好";   

8.  byte[] temp = {0};   

9.  try {   

10. byte[] smscontentdb = smscontentemp.getBytes("gbk");   

11. byte[] smscontent = new byte[smscontentdb.length + temp.length];   

12. System.arraycopy(smscontentdb, 0, smscontent, 0, smscontentdb.length);   

13. System.arraycopy(temp, 0, smscontent, smscontentdb.length, temp.length);   

14. SendSMS sendSMS = new SendSMS();   

15. sendSMS.SmsInit();   

16. if (sendSMS.SmsSend(mobileno, smscontent) >= 0) {   

17. System.out.println("chenggong !");   

18. }   

19. else {   

20. System.out.println("shibai !");   

21. }   

22. }catch (Exception ex) {}   

23. }   

24. }  

 

在这个文件中要注意的有一点,就是在传递字节数组到C程序中时,最后的结尾一定要以0结束。这是一个偷懒的做法,不过是个有效的做法。因为大多数情况下,接口是由第三方提供的。所以我们一般是不知道在C的方法里,具体是怎么处理参数的。而C又是要求数组是有长度。所以,在Java中,如果你不想写程序传数组的长度,那么在数组中以0结尾就是最方便的方法了。当然,如果有更好的方法也希望大家提出。
到这里,一个完整的Java通过JNI调用动态链接库的程序就完成了。实际上也不是很复杂。只要多注意一下细节,是很容易得出来的。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值