JNI用法小结
因项目需要学习了用JNI调用动态库的方法,现总结如下。
这次学到了两个知识点:
1. 调用第三方动态库的接口;
2. 回调java自身或自定义的函数;
名词介绍:
JNI 是 Java Native Interface 的缩写,用以解决 Java 对本地方法的操作问题,而本地方法是以库文件的形式存放的(.dll for windows,.so for Unix)。
Native是JNI关键字,用来声明要调用本地的方法。
实现过程
1) 编写含有native声明的方法的java类
如我要调用研华1780动态库里的初化函数与发脉冲函数,则写如下的类:
package signalContrl;
public class SignalContrl {
static{
System.loadLibrary("signalCtrl");
}
public native boolean ini ();
public native boolean startCounter(int channel,int rate);
public native boolean startFreq(int channel,int FoutSrc,int usDivider);
public native boolean stopCounter();
public native boolean stopFreq();
}
其中System.loadLibrary(“signalCtrl”);是声明要调用的库的名字为“signalCtrl”.
其中用到关键字native,说明这些方法都是要调用本地动态库里的函数。
2) 使用javac命令编译所编写的java类
在安装了jdk的情况下(我装的jdk1.5),切换到dos界面,转到存放SignalCtrl.java的目录下,调用javac SignalCtrl.java。然后当前目录下就会出现了个 SignalCtrl.class文件。
3) 使用javah java类名生成扩展名为h的头文件
在dos界面输入命令“cd ..”切换到上级目录。
然后输入命令“javah signalCtrl.SignalCtrl”。其中signalCtrl为包名,SignalCtrl为类名。命令执行完后,在当前目录就会生成“signalContrl_SignalContrl.h”文件。
4) 使用C++实现本地方法
在Visual C++中新建一个空的动态库工程,命名为“signalCtrl”,将上面产生的“signalContrl_Signal-Contrl.h”添加到工程中,然后根据需要实现头文件中的方法。
例如实现控件1780发脉冲的方法如下:
JNIEXPORT jboolean JNICALL Java_signalContrl_SignalContrl_startCoun -ter(JNIEnv *, jobject, jint channel,jint rate)
{
//开启计数功能的函数
usChan = channel;
ptCounterPulseStart.counter = channel;
ptCounterPulseStart.period = (float)(1.0/rate);
ptCounterPulseStart.UpCycle = ptCounterPulseStart.period/2;
ptCounterPulseStart.GateMode = 0;
if((ErrCde = DRV_CounterPulseStart(lDriverHandle,
(LPT_CounterPulseStart)&ptCounterPulseStart)) != 0)
{
DRV_GetErrorMessage(ErrCde,(LPSTR)szErrMsg);
return false;
}
startNum++;
return true;
}
上例中,函数名是从“signalContrl_Signal-Contrl.h”文件中拷贝过来的,然后函数体内通过对参数的进行处理,然后调用1780的动态库里的函数“DRV_CounterPulseStart()”。这样就成功使用了1780的动库。
5) 将C++编写的文件生成动态连接库
用Visual C++编译写好的工程,这时在Debug目录中就会生成一个signalCtrl.dll的文件。将此文件拷贝到需要的java项目的工作目录下,这样使用jni的工作就做完了。此后你就可以调用“SignalContrl”类里的方法,来实现对1780的控制。测试代码如下:
package testSignalContrl;
import signalContrl.SignalContrl;
public class GaneratorSignal {
SignalContrl sc = new SignalContrl();
public static void main(String[] args) {
GaneratorSignal gS = new GaneratorSignal();
boolean flag = gS.sc.ini();
if(!flag)
{
System.out.print("打开失败");
}
flag = false;
flag = gS.sc.startCounter(0,500);
//gS.sc.startFreq(0, 7, 13);
if(!flag)
{
System.out.print("开始发脉冲失败!请重新启动");
}
}
}
上例中,首先实例化了一个GaneratorSignal类gS, 然后由gS调用变量sc的方法ini()初始化1780卡,然后调用startCounter()使用1780卡发送脉冲。
面对基础的调用,上面几步就可以实现对本地方法的调用了。但是java里面有很多地方用到了回调,为了能根据动态库的使用情况,实时的回调java已经编写好的方法,我实现了jni函数的回调功能。基体做法如下:
a) 首先定义一个接口:
package MyCallback;
public interface Car {
public void run();
}
b) 实现这个接口:
package MyCallback;
public class Cars implements Car{
public void run() {
System.out.println("开车上班路上");
//回调方法中可以调用类似客户端的方法得到一些客户端的信息,如speed
}
public Cars get(){
System.out.println("创建Cars实例。");
return new Cars();
}
public void run(int i){
System.out.println(i);
}
}
编写jni类:
package MyCallback;
public class Employee {
static{
System.loadLibrary("Car");
}
public native void shangBan(Cars car);
public native void otherMethod();
}
上类中声明了两个方法:shangBan()和otherMethod();其中上shangBan()方法接收了一个java类Cars,做参数。在其C++实现中调用Cars类方法,实现回调。
在JNI里使用java类的某些方法,分为以下几个步骤:
1. 查找该类:
jclass xxx = (*env)->FindClass(env, "Lclass_name;");或是GetObjectClass();
2. 查找该类的初始化方法:
jmethodID xxx = (*env)->GetMethodID(env, jclass, "<init>", "(M)N");
3. 查找需要调用的该类的方法:
jmethodID xxx = (*env)->GetMethodID(env, jclass, "(M)N" );
4. 初始化该类的实例:
jobject xxx = (*env)->NewObject(env, jclass, jmethodID );
5. 调用实例的某方法:
(*env)->CallObjectMethod(env, jclass, jmethodID, [parameter1, parameter2,...] );
6. 释放实例:
(*env)->DeleteLocalRef(env, xxx);
将上类编译,然后生成头文件“MyCallback_Employee.h”用VisualC++实现该头文件中的shangBan方法,代码如下:
JNIEXPORT void JNICALL Java_MyCallback_Employee_shangBan
(JNIEnv *env, jobject obj, jobject car)
{
jint i = 23;
jclass cls = env->GetObjectClass(car);
jmethodID runId = env->GetMethodID(cls,"run","()V");
jmethodID numID = env->GetMethodID(cls,"run","(I)V");
jmethodID getId = env->GetMethodID(cls,"get","()LmyCallback /Cars;");
if (runId == 0)
return;
jobject emp = env->NewObject(cls,getId);
env->CallVoidMethod(emp,runId);
env->CallVoidMethod(emp,numID,i);
}
上面方法中,声明一个jclass cls用来接收传进的参数“car”。由env调用GetObjectClass(car)来获取传进来的对象。然后调用GetMethodID(cls,”run”,”()V”)来获取run函数的ID值,同理获取get()函数的ID值。再由NewObject新建一个类的实例emp,然后由CallVoidMethod(emp,runId)回调Cars实现的run函数。