JNI(Java Native Interface),具体定义我就不多说了。做java和android的开发者应该都知道这个东西。原来做项目的时候使用JNI调用过一个加了com组件的MFC窗口。现在需要通过JNI调用C++实现的函数,该函数每隔一段时间给Java端返回一些数据。今天复习了一下JNI,写了一个小的程序。
JNI其实就是把Java类中的方法在java端只保留方法的声明,把方法体也就是实现放到其他语言,如C++、matlab中。
小程序内容是:在C++端有一个int变量,每隔一段时间就+1,并把+1后的值传递给Java端,Java端接收到该数据以后显示到屏幕上。
具体思想:C++声明一个全局变量int num,两个函数一个counter、一个getNum。在counter里用for循环使num++以后Sleep1秒,getNum把num值返回给JNI。Java端使用两个线程,一个线程运行C++端的counter(),一个线程用while(ture)不停的查询num值,如果发现num改变就打印出来。
实现如下:
一、Java添加一个类JniTest
其中声明两个方法counter()和getNum(),不同的就是在声明的时候加上关键字native,代码如下:
package cn.edu.cuit.JNITest;
public class JniTest {
private native void counter();
private native int getNum();
}
二、编译Java类,生成JNI的头文件
cmd进入windows的命令行,使用cd进入到java的工程根目录,如图1:
图1 编译生成jni的头文件
在编译的时候要可以省略-jni这个参数,直接用javah就行了。此时要注意的是如果没有package cn.edu.cuit.JNITest则直接使用命令:
>javah JniTest
就可以编译了。但是把JniTest类放到包中以后就要在编译的时候加上包的路径,否则编译不过。
完成编译,查看工程根目录,发现多了一个C语言的头文件cn_edu_cuit_JNITest_JniTest.h。打开此文件内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class cn_edu_cuit_JNITest_JniTest */
#ifndef _Included_cn_edu_cuit_JNITest_JniTest
#define _Included_cn_edu_cuit_JNITest_JniTest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: cn_edu_cuit_JNITest_JniTest
* Method: counter
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_cn_edu_cuit_JNITest_JniTest_counter
(JNIEnv *, jobject);
/*
* Class: cn_edu_cuit_JNITest_JniTest
* Method: getNum
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_cn_edu_cuit_JNITest_JniTest_getNum
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
注意:把#include改成 #include"jni.h"否则后面会遇到编译错误。
在上面的头文件中有两个函数的声明:
JNIEXPORT void JNICALL Java_cn_edu_cuit_JNITest_JniTest_counter (JNIEnv *, jobject);
JNIEXPORT jint JNICALL Java_cn_edu_cuit_JNITest_JniTest_getNum (JNIEnv *, jobject);
就是我们在java端声明的方法对应的JNI的函数声明。
三、用vs2010建立win32的DLL工程,实现c++端的函数体。
vs2010 -> 新建项目 -> Win32项目 -> 下一步 ->应用程序类型选择DLL -> 完成。
在java的jdk安装目录下的jdk1.*** \ \include \\ 下面找到jni.h
在java的jdk1.*** \ \include \\ win32 \\ 下面找到jni_md.h
把两个文件拷贝到刚才建立的C++的工程目录下。
注意用vs2010会有两个JniTest目录,JniTest \\ JniTest 第一个是解决方案的目录,第二哥是工程目录,不要放错位置了。
把刚才生成的cn_edu_cuit_JNITest_JniTest.h也拷贝到JniTest工程目录下面。
在C++的工程头文件中添加这三个头文件;在源文件中建立以个JniTest.cpp的文件,如图2:
图2 c++程序的工程文件
在JniTest.cpp 实现这两个方法:
#include "stdafx.h"
#include "jni.h"
#include
#include
#include "cn_edu_cuit_JNItest_JniTest.h"
using namespace std;
int num = 0;
JNIEXPORT void JNICALL Java_cn_edu_cuit_JNITest_JniTest_counter
(JNIEnv *, jobject)
{
for(int i=0; i<100; i++)
{
num++;
Sleep(1000);
}
}
JNIEXPORT jint JNICALL Java_cn_edu_cuit_JNITest_JniTest_getNum
(JNIEnv *, jobject)
{
return num;
} 代码很简单我就不解释了,注意添加相关的.h文件。
编译生成dll文件,注意生成的dll文件是放在解决方案的Debug目录里面,不是在工程目录的Debug里面。之前做JNI用的是VC6.0,生成的dll是在工程目录下的Debug下,用vs2010要注意这个问题。
在Debug目录下发现JniTest.cpp生成的JniTest.dll文件,把它拷贝到java的工程目录>Chart0.6下,也可以拷贝到一个自己专门存放dll的目录中,并在windows的环境变量中添加这个目录。
四、完善Java端代码
在Java类JniTest中load这个dll类库,代码如下;
public class JniTest {
private native void counter();
private native int getNum();
static{
System.loadLibrary("JniTest");
}
然后在继续在类中添加main函数,代码如下:
package cn.edu.cuit.JNITest;
public class JniTest {
private native void counter();
private native int getNum();
static{
System.loadLibrary("JniTest");
}
static int oldNum;
public static void main(String[] args)
{
final JniTest jniTest = new JniTest();
Runnable printRunnable = new Runnable() {
public void run() {
while(true)
{
int newNum = jniTest.getNum();
if(newNum != oldNum)
{
System.out.println(newNum);
oldNum = newNum;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
Thread printThread = new Thread(printRunnable);
Runnable counterRunnable = new Runnable() {
public void run() {
jniTest.counter();
}
};
Thread counterThread = new Thread(counterRunnable);
printThread.start();
counterThread.start();
}
}
main函数中启用两个线程,一个counterThread中调用JniTest类中的counter方法,另一个线程printThread每个0.5秒调用一次getNum,得到一个newNum值,与保存的oldNum值做比较,如果相同则说明C++端的num还没有变化,如果不同则打印出这个值,并更新oldNum的值。
Java在传String给C++或者C语言的时候的处理很多地方都找的到,就是使用GetStringUTFChars和NewStringUTF,我就不详述了。