JNI——Java调用C/C++函数
1. 编写并编译Java程序
javac J2C.java => J2C.class
2. 生成C/C++头文件
javah J2C => J2C.h (安装JDK后,$JAVA_HOME应该已加入$PATH, 否则使用绝对路径,例如/usr/bin/javah)
3. 编写对应的C/C++程序:J2C.c
4. 生成C/C++目标文件
gcc -I/usr/lib/jvm/java-6-openjdk-amd64/include -I/usr/lib/jvm/java-6-openjdk-amd64/include/linux -fPIC -c J2C.c => J2C.o
5. 生成C/C++共享库
gcc -shared -Wl,-soname,libj2c.so.1 -o libj2c.so.1.0 J2C.o => libj2c.so.1.0
6. 重命名cp libj2c.so.1.0 libj2c.so => libj2c.so
7. 将共享库加入动态链接库的路径(此例为当前目录)
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
8. 执行Java程序,实现跨语言通信
java J2C
五、具体过程
1. 编写并编译J2C.java
1 import java.lang.management.ManagementFactory; 2 import java.lang.management.RuntimeMXBean; 3 4 public class J2C 5 { 6 static 7 { 8 try{ 9 // 此处即为本地方法所在链接库名 10 System.loadLibrary("j2c"); 11 } catch(UnsatisfiedLinkError e) 12 { 13 System.err.println( "Cannot load J2C library:\n " + 14 e.toString() ); 15 } 16 } 17 18 //声明的本地方法 19 public static native int write2proc(int pid); 20 21 public static void main(String[] args){ 22 23 //获取本进程(即主线程)的pid 24 final RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean(); 25 final String info = runtime.getName(); 26 final int index = info.indexOf("@"); 27 28 29 if (index != -1) { 30 final int pid = Integer.parseInt(info.substring(0, index)); 31 System.out.println(info); 32 System.out.println(pid); 33 34 write2proc(pid); 35 } 36 37 38 try{ 39 Thread.sleep(8000); 40 } catch(InterruptedException e){ 41 e.printStackTrace(); 42 } 43 } 44 }
note:Java程序中System.loadLibrary参数名表示要载入的C/C++共享库,第6步生成的共享库名必须与该参数一致,
即System.loadLibrary(Name) 对应共享库名libName.so (共享库名必须以lib开头)
2. 生成C头文件J2C.h:javah J2C
1 /* DO NOT EDIT THIS FILE - it is machine generated */ 2 #include <jni.h> 3 /* Header for class J2C */ 4 5 #ifndef _Included_J2C 6 #define _Included_J2C 7 #ifdef __cplusplus 8 extern "C" { 9 #endif 10 /* 11 * Class: J2C 12 * Method: write2proc 13 * Signature: (I)I 14 */ 15 JNIEXPORT jint JNICALL Java_J2C_write2proc 16 (JNIEnv *, jclass, jint); 17 18 #ifdef __cplusplus 19 } 20 #endif 21 #endif
note:1. 头文件自动生成,不要修改它;
2. 函数JNIEXPORT jint JNICALL Java_J2C_write2proc(JNIEnv *, jclass, jint);
按照注释的说明是在J2C.java文件的类J2C的方法write2proc处定义,故C程序的实现函数必须与该处签名一致;
3. 编写C程序J2C.c
1 #include <stdio.h> 2 3 #include "J2C.h" 4 5 JNIEXPORT int JNICALL Java_J2C_write2proc(JNIEnv * env, jobject arg, jint pid) 6 { 7 8 printf("current pid is %d\n", pid); 9 10 return 0; 11 12 }
4. 编译C程序
因为C程序里#include "J2C.h"而J2C.h又#include <jni.h>, 而gcc里面默认环境并不知道jni.h是什么东西,故编译时需要告诉编译器jni.h的位置( jni.h在jdk 的$JAVA_HOME/include下面),所以才有了上面的编译参数;
因为使用gcc编译得到动态库,在jni调用的时候,某些情况会有异常, 可尝试改用g++。
后续的5、6、7、8同前文所述。
六、总结
1. Java中方法的原型声明与C/C++对应的实现文件定义必须一致(可以通过自动生成的C/C++头文件来比较),尤其是类名和方法名;
2. Java中System.loadLibrary()载入的共享库名必须与后面C/C++生成的共享库名一致;
3. JNI步骤很清晰,但是很多小细节容易引起各种问题,so细心点。
附上C/C++中Java方法签名及常见的错误:
1. 在C/C++函数中实现Java中定义的方法
函数声明, Java_完整类名_方法名, 完整类名包括了包名.
在注释中我们可以看到这样一个东西 Signature, 这个是方法的签名. 关于Signature, 下面通过一个表格来说明.
java类型 | Signature | 备注 |
boolean | Z | |
byte | B | |
char | C | |
short | S | |
int | I | |
long | L | |
float | F | |
double | D | |
void | V | |
object | L用/分割的完整类名 | 例如: Ljava/lang/String表示String类型 |
Array | [签名 | 例如: [I表示int数组, [Ljava/lang/String表示String数组 |
Method | (参数签名)返回类型签名 | 例如: ([I)I表示参数类型为int数组, 返回int类型的方法 |
如本例中的函数声明:
JNIEXPORT jint JNICALL Java_J2C_write2proc(JNIEnv *, jobject, jint);
注释中的签名是 Signature: (I)I
2. JNI开发中碰到的"error: request for member 'GetStringUTFChars' in something not a structure or union"
这个错误在C、C++中需要分开处理。
C代码: (*env)->GetStringUTFChars(env, string, 0);
C++代码: env->GetStringUTFChars(string, 0);
原因:C语言中使用的是结构体的函数指针, 而在C++中使用的还是struct, 我们知道struct在C++中和class的功能是几乎一样的, struct也可以用来定义类, 所以env在C++中是个类对象的指针.
下面以IBM的一篇经典文章为例 http://www.ibm.com/developerworks/cn/java/l-linux-jni/
C++:
// 从 instring 字符串取得指向字符串 UTF 编码的指针
const char *str = env->GetStringUTFChars( instring, JNI_FALSE );
printf("Hello,%s\n",str);
// 通知虚拟机本地代码不再需要通过 str 访问 Java 字符串。
env->ReleaseStringUTFChars( instring, str );
C:
// 从 instring 字符串取得指向字符串 UTF 编码的指针
const char *str = (*env)->GetStringUTFChars( env, instring, JNI_FALSE );
printf("Hello,%s\n",str);
// 通知虚拟机本地代码不再需要通过 str 访问 Java 字符串。
(*env)->ReleaseStringUTFChars( env, instring, str );
3. 当使用 JNI 从 Java 程序访问本机代码时,您会遇到许多问题。您会遇到的三个最常见的错误是:
无法找到动态链接:Exception in thread "main" java.lang.UnsatisfiedLinkError
它所产生的错误消息是:java.lang.UnsatisfiedLinkError。这通常指无法找到共享库,或者无法找到共享库内特定的本机方法。
无法找到共享库文件:Cannot load library
当用 System.loadLibrary(String libname) 方法(参数是文件名)装入库文件时,请确保文件名拼写正确以及没有指定扩展名。还有,确保库文件的位置在类路径中,从而确保 JVM 可以访问该库文件。
无法找到具有指定说明的方法
确保您的 C/C++ 函数实现拥有与头文件中的函数说明相同的说明。
How to resolve java.lang.UnsatisfiedLinkError
User should check whether-
1. System.loadLibrary is passed an incorrect parameter:
- Windows: To load Name.dll, Name is passed to the loadLibrary method.
-AIX, HP-UX, Solaris, Linux: To load libName.so or libName.a, libName is passed to the loadLibrary method
2. Native library is already loaded-
If the native library was already loaded by an application and the same application tries to load it again, this can cause this error.
3. Native Library is not present in java.library.path or LD_LIBRARY_PATH
参考:
http://www.ibm.com/developerworks/cn/java/l-linux-jni/
http://www.cnblogs.com/icejoywoo/archive/2012/02/22/2363709.html
http://www.cnblogs.com/mimi1/archive/2012/09/27.html