本文章非原创,多数内容参考以下两篇博客,感谢。由于我在实践中遇到一些问题,所以打算做一些补充,重新写一篇更完整的JNI入门文章。
使用JNI进行Java与C/C++语言混合编程(1)--在Java中调用C/C++本地库
Java语言与C语言混合编程(1)--Java native 关键字
一、编写java文件:创建native method
简单地讲,一个 native Method 就是一个java调用非java代码的接口。表示一个方法的实现在java以外,已经在本地实现了。
如果想更加了解 native Method 可以查看 Java语言与C语言混合编程(1)--Java native 关键字。
下面我们举个例子。
package myJNI;
public class myJNI {
public native int intMethod(int n);
public native boolean booleanMethod(boolean bool);
public native String stringMethod(String text);
public native int intArrayMethod(int[] intArray);
public static void main(String[] args) {
System.loadLibrary("myJNIdll");//注意: 不可以在代码中写上后缀 .dll 或 .so
myJNI sample = new myJNI();
int square = sample.intMethod(5);
boolean bool = sample.booleanMethod(true);
String text = sample.stringMethod("Java");
int sum = sample.intArrayMethod(new int[]{1,2,3,4,5,8,13});
System.out.println("intMethod:" + square);
System.out.println("booleanMethod:" + bool);
System.out.println("stringMethod:" + text);
System.out.println("intArrayMethod:" + sum);
}
}
上面有4个native方法, 4个native方法就是我们需要用C来实现的方法.
分别是4种类型的参数, int, boolean, String, int[].
其中有一句比较重要, 这句话加载了动态类库
System.loadLibrary("myJNI");
在windows下加载的就是myJNI.dll, 在Linux下加载的就是myJNI.so.
本文使用的windows, 所以后面使用myJNI.dll来表示myJNI动态链接库.
编译myJNI.java, 使用命令行(windows是cmd, linux下一般是bash)
javac myJNI.java
可以看到myJNI.class文件
二、使用javah生成头文件
javah myJNI.myJNI
注意带上包名,我们的文件一般有包名。我的例子里包名是myJNI,在Java文件里可以看到。这和java命令一样。平时IDE用多了容易忘记。
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class myJNI_myJNI */
#ifndef _Included_myJNI_myJNI
#define _Included_myJNI_myJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: myJNI_myJNI
* Method: intMethod
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_myJNI_myJNI_intMethod
(JNIEnv *, jobject, jint);
/*
* Class: myJNI_myJNI
* Method: booleanMethod
* Signature: (Z)Z
*/
JNIEXPORT jboolean JNICALL Java_myJNI_myJNI_booleanMethod
(JNIEnv *, jobject, jboolean);
/*
* Class: myJNI_myJNI
* Method: stringMethod
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_myJNI_myJNI_stringMethod
(JNIEnv *, jobject, jstring);
/*
* Class: myJNI_myJNI
* Method: intArrayMethod
* Signature: ([I)I
*/
JNIEXPORT jint JNICALL Java_myJNI_myJNI_intArrayMethod
(JNIEnv *, jobject, jintArray);
#ifdef __cplusplus
}
#endif
#endif
我们可以看到其中有四个函数声明, 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_myJNI_myJNI_intMethod (JNIEnv *, jobject, jint);
上方注释中的签名是 Signature: (I)I
在每个函数的参数列表中都有JNIEnv *和 jobject两个参数, 这两个参数稍候说明.
三、编辑DLL文件
1.处理头文件之间的联系
对 jni.h 文件做一些更改,#include <jni_md.h>改为 #include "C:\Program Files\Java\jdk1.8.0_101\include\win32\jni_md.h"
对我们用 javah 生成的头文件的开头部分 include 语句也要用双引号加文件名。自动生成的是如下样子,改为
#include "C:\Program Files\Java\jdk1.8.0_101\include\jni.h"
Java 的安装目录里。我们把这两个文件放到了我们的 DLL project 里,如果不用双引号将会差找不到。
深入学习的话去了解 include 语句。
include 关系为 dll.h include jni.h , jni.h include jni_md.h
也可以直接用 #include "jni_md.h" 和 #include "jni.h",要把这两个文件和dll.h放在一个目录下。
然后把我们生成的头文件 myJNI_myJNI.h 的内容全部复制粘贴到 dll.h 文件里,粘贴到下图中选中的位置。
为什么不直接用myJNI_myJNI.h 文件呢?因为如果没有上图里的那几句话,dll 文件是无法编译输出的。你也可以把这几句话
放进myJNI_myJNI.h 文件。
2.编写 c 程序
#include "dll.h"
#include <string.h>
JNIEXPORT
jint JNICALL JNICALL Java_myJNI_myJNI_intMethod
(JNIEnv
*env, jobject obj, jint num)
{
return num
* num;
}
JNIEXPORT
jboolean JNICALL JNICALL Java_myJNI_myJNI_booleanMethod
(JNIEnv
*env, jobject obj, jboolean boolean)
{
return !boolean;
}
JNIEXPORT
jstring JNICALL JNICALL Java_myJNI_myJNI_stringMethod
(JNIEnv
*env, jobject obj, jstring string)
{
const char*
str = (*env)->GetStringUTFChars(env, string, 0);
char cap[128];
strcpy(cap,
str);
(*env)->ReleaseStringUTFChars(env,
string, 0);
return (*env)->NewStringUTF(env,
strupr(cap));
}
JNIEXPORT
jint JNICALL JNICALL Java_myJNI_myJNI_intArrayMethod
(JNIEnv
*env, jobject obj, jintArray array)
{
int i,
sum = 0;
jsize
len = (*env)->GetArrayLength(env, array);
jint
*body = (*env)->GetIntArrayElements(env, array, 0);
for (i
= 0; i < len; ++i)
{
sum
+= body[i];
}
(*env)->ReleaseIntArrayElements(env,
array, body, 0);
return sum;
}
(*env)->GetStringUTFChars()这个方法, 是用来在Java和C之间转换字符串的, 因为Java本身都使用了双字节的字符, 而C语言本身都是单字节的字符,
所以需要进行转换.
JNIEnv *是每个函数都有的参数, 它包含了很多有用的方法, 使用起来类似Java的反射, 也提供了这样一个编码转换的函数.
GetStringUTFChars()和NewStringUTF(), 第一个是从UTF8转换为C的编码格式, 第二个是根据C的字符串返回一个UTF8字符串.
ReleaseStringUTFChars()是用来释放对象的, 在Java中有虚拟机进行垃圾回收, 但是在C语言中, 这些对象必须手动回收. 否则可能造成内存泄漏.
函数的名字一眼看到就可以猜出功能, jni.h中的大部分函数名都是这样.
之后编译就好了。如果编译不出问题就OK了。