本章用Hello World示例带你领略JNI编程。
2.1 Overview
准备过程:
1. 创建一个类(HelloWorld.java)
2. 使用javac编译该类
3. 利用javah -jni产生头文件
4. 用本地代码实现头文件中定义的方法
5. Run
译注:
在一个特定环境中,写本地实现的过程是不同的(如Android)。
javah主要是生成头文件和函数签名(每个方法和成员都有签名,后有详细介绍),通过
javah学习如何正确的写法。
注意:如上述HelloWorld.java,编译后的文件为HelloWorld.class, 用 $javah HelloWorld
来产生头文件,不要带末尾的".class"。
2.2 Declare the Native Method
HelloWorld.java
class HelloWorld {
private native void print();
public static void main(String[] args) {
new HelloWorld().print();
}
static {
System.loadLibrary("HelloWorld");
}
}
HelloWrold类首先声明了一个private native print方法. static那几行是本地库。
在Java代码中声明本地方法必须有"native"标识符,native修饰的方法,在Java代码中
只作为声明存在。
在调用本地方法前,必须首先装载含有该方法的本地库. 如HelloWorld.java中所示,置
于static块中,在Java VM初始化一个类时,首先执行这部分代码,这可保证调用本地方
法前,装载了本地库。
装载库的机制,后有介绍。
2.3 Compile the HelloWorld Class
$javac HelloWorld.java
2.4 Create the Native Method Header File
$javah -jni HelloWorld
译者:"-jni"为默认参数,可有可无.
上述命令,生成HelloWorld.h文件. 关键部分如下:
JNIEXPORT void JNICALL
Java_HelloWorld_print (JNIEnv *, jobject);
现在,请先忽略两个宏:JNIEXPORT和JNICALL。 你会发现,该函数声明,接受两个参数,
而对应的Java代码对该函数的声明没有参数。第一个参数是指向JNIEnv结构的指针; 第
二个参数,为HelloWorld对象自身,即this指针。
译注:
JNIEnv是JNI核心数据之一,地位非常崇高,所有对JNI的调用都要通过此结构。
2.5 Write the Native Method Implementation
必须根据javah生成的本地函数声明实现函数,如下:
#include <jni.h>
#include <stdio.h>
#include "HelloWorld.h"
JNIEXPORT void JNICALL
Java_HelloWorld_print(JNIEnv *env, jobject obj)
{
printf("Hello World!\n");
return;
}
请注意:"jni.h"文件必须被包含,该文件定义了JNI所有的函数声明和数据类型。
2.6 Compile the C Source and Create a Native Library
请注意,生成的本地库的名字,必须与System.loadLibrary("HelloWorld");待装载库
的名字相同。
Solaris:
$cc -G -I/java/include -I/java/include/solaris HelloWorld.c -o libHelloWorld.so
-G: 生成共享库
Win:
$cl -Ic:\java\include -Ic:\java\include\win32 -MD -LD HelloWorld.c -FeHelloWorld.dll
-MD:保证与Win32多线程C库连接(译者:Win上分静态、动态、动态多线程...C库)
-LD: 生成动态链接库
2.7 Run the Program
Solaris or Win:
$java HelloWorld
输出:
Hello World!
运行前,必须保证连接器,能找到待装载的库,不然,将抛如下异常: java.lang.UnsatisfiedLinkError: no HelloWorld in library path
at java.lang.Runtime.loadLibrary(Runtime.java)
at java.lang.System.loadLibrary(System.java)
at HelloWorld.main(HelloWorld.java)
第二篇:JNI使用小例子(windows下java调用C++动态链接库)-LXL
用JNI调用C++动态链接库
编程环境:
Windows操作系统
jdk1.5
Eclipse
VC6.0
一、使用 Java 的 JNI 调用 C/C++ 的动态联结库有个固定的步骤,下面将以一个最简单的 HelloWorld 例子程序来说明调用过程。这个 HelloWorld 的例子只是简单的显示 HelloWorld 消息。 通过这个例子可以了解调用流程。
第一步、编写java类
首先,创建一个名为jnitest的Eclipse工程,并新建一个src源代码目录。这里我们编写两个java类,一个命名为HelloWorld,一个命名为TestDLL。HelloWorld中包含一个本地方法displayHelloWorld(),TestDLL用于测试这个本地方法。HelloWorld的源码如下:
HelloWorld.java 源码
public class HelloWorld {
public native void displayHelloWorld(); ①
static {
//System.loadLibrary( "HelloWorld" ); ②
System.load(System.getProperty("user.dir") + "\\PositionCorrect.dll");③ }
}
TestDLL 源码如下:
TestDLL.java 源码
public class TestDLL {
public static void main(String[] args){
HelloWorld helloWorld = new HelloWorld();
helloWorld.displayHelloWorld();
}
}
【说明】:
(1)是java本地方法申明;
(2)" HelloWorld " 是要装入的动态链接库名称。
装入动态链接库, 使用这种方法加载的话,dll文件存放的位置和JVM搜索的路径都要注意,先将生成的 DLL 拷贝到 jnitest 工程的D:\workspace\jnitest\bin 下面,然后设置 DLL 搜索路径(环境变量 PATH )为 D:\workspace\jnitest\bin ( JNI 并没有规定必须将 dll 放在与 class 文件相同的目录,只要确保 JVM 能搜索到 DLL 即可)③。重新启动 Elipse 后,运行测试程序,将看到控制台输出“ Hello World !”
注意,③必须设置好 DLL 搜索路径,或者将 dll 拷贝到
C:\WINDOWS\system32 下面。这里,我折腾了不少时间,才搞定。
(3)System.getProperty("user.dir")获得的是当前工程目录,即
D:\JAVA\workspace\jnitest
第二步、编译Java类
在Eclipse工具中编译Java类,编译后的class文件会自动放入工程目录下的bin目录中,在我的例子中,路径是D:\JAVA\workspace\jnitest\bin。
第三步、生成C++头文件
在windows中打开一个DOS命令窗口,我电脑上DOS的默认路径是C:\Users\s001,此处可以不修改默认路径,后面生成的.h文件就会自动放在改路径下,如果这个默认路径不好找,可以输入d:命令,将路径修改为D盘下,路径了解后,就要键入javah –classpath D:\JAVA\workspace\jnitest\bin HelloWorld 命令生成头文件HelloWorld.h。内容如下:
HelloWorld.h 源码
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */
#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloWorld
* Method: displayHelloWorld
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
注意,不要修改这个头文件。
第四步、在VC中编写本地方法实现
用VC6创建名为HelloWorldWin32动态链接库工程(Dynamic Link Library)。将上述头文件HelloWorld.h拷贝到HelloWorld工程目录下,
将C:\Program Files\Java\jdk1.5.0_06\include目录下的jni.h和
C:\Program Files\Java\jdk1.5.0_06\include\win32目录下的jni_md.h
拷贝到C:\Program Files\Microsoft Visual Studio\VC98\Include目录下。在VC IDE中新建HelloWorld.cpp文件,实现头文件中声明的方法并编译成DLL。 HelloWorld.cpp 源码
#include "HelloWorld.h"
#include <stdio.h>
/*
* Class: HelloWorld
* Method: displayHelloWorld
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld
(JNIEnv *, jobject)
{
printf( "Hello World!\n" );
return ;
}
第五步、在Java中运行测试程序
先将生成的DLL拷贝到jnitest工程的目录下D:\JAVA\workspace\jnitest,刷新一下java工程,Run As Java Application即可。
二、传整型返回整型
1、总体说明:实现一个函数,传入整型数i,返回值i*i,这个函数的实现放在DLL中,Java通过JNI调用这个函数,传入一个参数,得到返回值。确定DLL的名称为calldll.dll,函数shanfei(int i)。
2、编写java程序javacall.java,如下:
public class javacall
{
static
{
System.loadLibrary("calldll");
}
public native static int shanfei(int i);
public static void main(String[] args)
{
javacall jc = new javacall();
int j;
j = jc.shanfei(4);
System.out.println(j);
}
}
3、编译成class文件
4、生成头文件
5、制作VC++动态链接库
建立新DLL工程,程序中添加:
#include "javacall.h"
JNIEXPORT jint JNICALL Java_javacall_shanfei (JNIEnv *, jclass, jint p) // jclass应该是jobject,这里要与头文件中此类型保持一致
{
int j = p*p;
return j;
}
6、java调用
三、传入两个整型数,返回整型数组
1、总体说明:在java中要传入两个int数给本地方法,在VC中对本地方法进行实现,并返回int数组,在java中得到本地方法返回的int[]并打印出来.
2、编写java程序PositionCorrect.java
public class PositionCorrect {
public native int[] GpsCoorEncrypt(int i,int j);
static {
if(isWindowsOS()){
}
else{
}
}
/**
} * 判断当前操作是否Windows. * * @return true---是Windows操作系统 */ public static boolean isWindowsOS(){ boolean isWindowsOS = false; String osName = System.getProperty("os.name"); if(osName.toLowerCase().indexOf("windows")>-1){ isWindowsOS = true; } return isWindowsOS; } System.load(System.getProperty("user.dir") + "/PositionCorrect.dll"); System.load(System.getProperty("user.dir") + "\\PositionCorrect.dll");
2、编译成class文件
3、生成头文件PositionCorrect.h,源码如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class PositionCorrect */
#ifndef _Included_PositionCorrect
#define _Included_PositionCorrect
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: PositionCorrect
* Method: GpsCoorEncrypt
* Signature: (II)[I
*/
JNIEXPORT jintArray JNICALL Java_PositionCorrect_GpsCoorEncrypt (JNIEnv *, jobject, jint , jint);
#ifdef __cplusplus
}
#endif
#endif
【说明】
(1)jintArray对应Java中的int[]
(2)Java_PositionCorrect_GpsCoorEncrypt
Java指Java程序;PositionCorrect指java中包含本地方法的类;GpsCoorEncrypt指要实现的本地方法,此处在Eclipse中创建的类放在了缺省包中,如果将PositionCorrect.java放在包JNIGPS下,则头文件相应的位置就变成
Java_JNIGPS_PositionCorrect_GpsCoorEncrypt
生成头文件时路径不变,运行的文件要改成:包名.class文件
(3)(JNIEnv *, jobject, jint , jint);
这一部分需要和下一步生成的.cpp文件中的方法的参数一致
4、制作VC++动态链接库
建立新DLL工程,程序中添加PositionCorrect.cpp文件,源码如下:
#include "PositionCorrect.h"
#include <stdio.h>
JNIEXPORT jintArray JNICALL Java_PositionCorrect_GpsCoorEncrypt (JNIEnv *env, jobject obj, jint lLongitude, jint lLatitude) {
jintArray iarr;// 定义数组对象
jint length = 2;
iarr =env->NewIntArray(length);//新建一个jintArray
jint buf[]={lLongitude,lLatitude};//定义一个jint类型的buffer把原始
的数组copy到这个buf中去
//返回一个jint类型的数组
//可以先往一个数组中输入值,然后把这个数组copy到jintArray中 env->SetIntArrayRegion(iarr, 0, length, buf);//将buf中的值复制到jintArray中去,数组copy
for(int i=0; i<length; i++)
printf("ELEMENT %d IS %d\n", i, buf[i]);
printf( "The end!\n" );
return iarr;
}
编译后生成dll
5、java调用,将dll文件放在工程目录下,编写使用本地方法的java文件 主要代码如下:
double x = 121.508652;
double y = 38.877323;
int longx = (int) (x * 100000);
int longy = (int) (y * 100000);
int xy[] = new int[2];
xy = positionCorrect.GpsCoorEncrypt(longx,longy);
for(int i=0;i<xy.length;i++)
System.out.println(xy[i]);
longx = xy[0];
longy = xy[1];
x = longx/100000.0;
y = longy/100000.0;
String sx,sy,s;
sx = String.valueOf(x);
sy = String.valueOf(y);
s = "经度:" + sx +"纬度:" + sy;
System.out.println(s);
问题:
1、Exception in thread "Thread-4" java.lang.UnsatisfiedLinkError: GpsCoorEncrypt
可能是VC中cpp文件的函数名与头文件中的不对应