(4.1.27.4)NI/NDK开发指南(一)——JVM查找java native方法的规则

来源:互联网 发布:淘宝店铺图片多大尺寸 编辑:程序博客网 时间:2024/06/05 03:55

 前言:

Java在调用native(本地)方法之前,需要先加载动态库。如果在未加载动态之前就调用native方法,会抛出找不到动态链接库文件的异常。如下所示:

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. Exception in thread "main" java.lang.UnsatisfiedLinkError: com.study.jnilearn.HelloWorld.sayHello(Ljava/lang/String;)Ljava/lang/String;  
  2.     at com.study.jnilearn.HelloWorld.sayHello(Native Method)  
  3.     at com.study.jnilearn.HelloWorld.main(HelloWorld.java:9)  

一般在类的静态(static)代码块中加载动态库最合适,因为在创建类的实例时,类会被ClassLoader先加载到虚拟机,随后立马调用类的static静态代码块。这时再去调用native方法就万无一失了。加载动态库的两种方式:

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. System.loadLibrary("HelloWorld");  
  2. System.load("/Users/yangxin/Desktop/libHelloWorld.jnilib");  
方式1:只需要指定动态库的名字即可,不需要加lib前缀,也不要加.so、.dll和.jnilib后缀

方式2:指定动态库的绝对路径名,需要加上前缀和后缀


事项:


如果使用方式1,java会去java.library.path系统属性指定的目录下查找动态库文件,如果没有找到会抛出java.lang.UnsatisfiedLinkError异常。

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. Exception in thread "main" java.lang.UnsatisfiedLinkError: no HelloWorld2 in java.library.path  
  2.     at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1860)  
  3.     at java.lang.Runtime.loadLibrary0(Runtime.java:845)  
  4.     at java.lang.System.loadLibrary(System.java:1084)  
  5.     at com.study.jnilearn.HelloWorld.<clinit>(HelloWorld.java:13)  
大家从异常中可以看出来,他是在java.library.path中查找该名称对应的动态库,如果在mac下找libHelloWorld.jnilib文件,linux下找libHelloWorld.so文件,windows下找libHelloWorld.dll文件,可以通过调用System.getProperties("java.library.path")方法获取查找的目录列表,下面是我本机mac os x 系统下的查找目录:

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. String libraryDirs = System.getProperty("java.library.path");  
  2. System.out.println(libraryDirs);  
  3. // 输出结果如下:  
  4. /Users/yangxin/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.  
有两种方式可以让java从java.library.path找到动态链接库文件,聪明的你应该已经想到了。

方式1:将动态链接库拷贝到java.library.path目录下

方式2:给jvm添加“-Djava.library.path=动态链接库搜索目录”参数,指定系统属性java.library.path的值

java -Djava.library.path=/Users/yangxin/Desktop

Linux/Unix环境下可以通过设置LD_LIBRARY_PATH环境变量,指定库的搜索目录



正文:


大家明白了调用native方法之前,首先要调用System.loadLibrary接口加载一个实现了native方法的动态库才能正常访问,否则就会抛出Java.lang.UnsatisfiedLinkError异常,找不到XX方法的提示。现在我们想想,在Java中调用某个native方法时,JVM是通过什么方式,能正确的找到动态库中C/C++实现的那个native函数呢?

       JVM查找native方法有两种方式:

       1> 按照JNI规范的命名规则

       2> 调用JNI提供的RegisterNatives函数,将本地函数注册到JVM中。(后面会详细介绍)

本文通过第一篇文章HelloWorld示例中的Java_com_study_jnilearn_HelloWorld_sayHello函数来详细介绍第一种方式:

[cpp] view plain copy
  1. /* DO NOT EDIT THIS FILE - it is machine generated */  
  2. #include <jni.h>  
  3. /* Header for class com_study_jnilearn_HelloWorld */  
  4.   
  5. #ifndef _Included_com_study_jnilearn_HelloWorld  
  6. #define _Included_com_study_jnilearn_HelloWorld  
  7. #ifdef __cplusplus  
  8. extern "C" {  
  9. #endif  
  10. /* 
  11.  * Class:     com_study_jnilearn_HelloWorld 
  12.  * Method:    sayHello 
  13.  * Signature: (Ljava/lang/String;)Ljava/lang/String; 
  14.  */  
  15. JNIEXPORT jstring JNICALL Java_com_study_jnilearn_HelloWorld_sayHello  
  16.   (JNIEnv *, jclass, jstring);  
  17.   
  18. #ifdef __cplusplus  
  19. }  
  20. #endif  
  21. #endif  

JNIEXPORT 和 JNICALL的作用:

在上篇文章中,我们在将HelloWorld.c编译成动态库的时候,用-I参数包含了JDK安装目录下的两个头文件目录:

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. gcc -dynamiclib -o /Users/yangxin/Library/Java/Extensions/libHelloWorld.jnilib jni/HelloWorld.c -framework JavaVM -I/$JAVA_HOME/include -I/$JAVA_HOME/include/darwin  
其中第一个目录为jni.h头文件所在目录,第二个是跨平台头文件目录(mac os x系统下的目录名为darwin,在windows下目录名为win32,linux下目录名为linux),用于定义与平台相关的宏,其中用于标识函数用途的两个宏JNIEXPORT 和 JNICALL,就定义在darwin目录下的jni_md.h头文件中。在Windows中编译dll动态库规定,如果动态库中的函数要被外部调用,需要在函数声明中添加__declspec(dllexport)标识,表示将该函数导出在外部可以调用。在Linux/Unix系统中,这两个宏可以省略不加。这两个平台的区别是由于各自的编译器所产生的可执行文件格式不一样。这里有篇文章详细介绍了两个平台编译的动态库区别:http://www.cnblogs.com/chio/archive/2008/11/13/1333119.html。JNICALL在windows中的值为__stdcall,用于约束函数入栈顺序和堆栈清理的规则。

Windows下jni_md.h头文件内容:

[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. #ifndef _JAVASOFT_JNI_MD_H_  
  2. #define _JAVASOFT_JNI_MD_H_  
  3.   
  4. #define JNIEXPORT __declspec(dllexport)  
  5. #define JNIIMPORT __declspec(dllimport)  
  6. #define JNICALL __stdcall  
  7.   
  8. typedef long jint;  
  9. typedef __int64 jlong;  
  10. typedef signed char jbyte;  
  11.   
  12. #endif   
Linux下jni_md.h头文件内容:

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. #ifndef _JAVASOFT_JNI_MD_H_  
  2. #define _JAVASOFT_JNI_MD_H_  
  3.   
  4. #define JNIEXPORT  
  5. #define JNIIMPORT  
  6. #define JNICALL  
  7.   
  8. typedef int jint;  
  9. #ifdef _LP64 /* 64-bit Solaris */  
  10. typedef long jlong;  
  11. #else  
  12. typedef long long jlong;  
  13. #endif  
  14.   
  15. typedef signed char jbyte;  
  16.   
  17. #endif  
从Linux下的jni_md.h头文件可以看出来,JNIEXPORT 和 JNICALL是一个空定义,所以在Linux下JNI函数声明可以省略这两个宏。

函数的命名规则:

用javah工具生成函数原型的头文件,函数命名规则为:Java_类全路径_方法名。如Java_com_study_jnilearn_HelloWorld_sayHello,其中Java_是函数的前缀,com_study_jnilearn_HelloWorld是类名,sayHello是方法名,它们之间用 _(下划线) 连接。


函数参数:

JNIEXPORT jstring JNICALL Java_com_study_jnilearn_HelloWorld_sayHello(JNIEnv *, jclass, jstring);

第一个参数:JNIEnv*是定义任意native函数的第一个参数(包括调用JNI的RegisterNatives函数注册的函数),指向JVM函数表的指针,函数表中的每一个入口指向一个JNI函数,每个函数用于访问JVM中特定的数据结构。

第二个参数:调用java中native方法的实例或Class对象,如果这个native方法是实例方法,则该参数是jobject,如果是静态方法,则是jclass

第三个参数:Java对应JNI中的数据类型,Java中String类型对应JNI的jstring类型。(后面会详细介绍JAVA与JNI数据类型的映射关系)


函数返回值类型:

夹在JNIEXPORT和JNICALL宏中间的jstring,表示函数的返回值类型,对应Java的String类型


总结:

        当我们熟悉了JNI的native函数命名规则之后,就可以不用通过javah命令去生成相应java native方法的函数原型了,只需要按照函数命名规则编写相应的函数原型和实现即可。

        比如com.study.jni.Utils类中还有一个计算加法的native实例方法add,有两个int参数和一个int返回值:public native int add(int num1, int num2),对应JNI的函数原型就是:JNIEXPORTjintJNICALLJava_com_study_jni_Utils_add(JNIEnv *,jobject, jint,jint); 




原文地址:http://blog.csdn.net/xyang81/article/details/41777471


0 0
原创粉丝点击