在Linux平台下使用JNI

来源:互联网 发布:linux 双网卡 编辑:程序博客网 时间:2024/05/11 08:59

本文简要介绍了JNI调用规范,及常用函数。并通过具体示例程序展示了实现一个本地调用的基本步骤。

引言

 

Java的出现给大家开发带来的极大的方便。但是,如果我们有大量原有的经过广泛测试的非Java代码,将它们全部用Java来重写,恐怕会带来巨大的工作量和长期的测试;如果我们的应用中需要访问到特定的设备,甚至是仅符合公司内部信息交互规范的设备,或某个特定的操作系统才有的特性,Java就显得有些力不从心了。面对这些问题,Sun公司在JDK1.0中就定义了JNI规范,它规定了Java应用程序对本地方法的调用规则。






实现步骤及相关函数使用

 

本文将一步步说明在Linux平台下如何实现本地共享库与Java协同工作。Hello World程序是目前标准的入门第一步,那么,我也以类似的应用最为样例。

第一步,定义一个 Java 类 -- Hello. 它提供SayHello方法:

此时应注意两点:

1.为要使用的每个本地方法编写本地方法声明,其声明方式与普通 Java 方法接口没什么不同,只是必须指定 native 关键字,如下所示:

public native void SayHello(String strName);

在这个函数中,我们将根据传进的人名,向某人问好。

2.必须显式地加载本地代码库。我们需在类的一个静态块中加载这个库:

static
{
System.loadLibrary("hello");
}

再加上必要的异常处理就生成如下源文件Hello.java:

public class Hello
{
static
{
try
{
//此处即为本地方法所在链接库名
System.loadLibrary("hello");
}
catch(UnsatisfiedLinkError e)
{
System.err.println( "Cannot load hello library:/n " +
e.toString() );
}
}
public Hello()
{
}
//声明的本地方法
public native void SayHello(String strName);
}

编译后生成Hello.class文件。

第二步,生成本地链接库。具体过程如下:

1.要为以上定义的类生成 Java 本地接口头文件,需使用javah,Java 编译器的 javah 功能将根据Hello类生成必要的声明,此命令将生成Hello.h文件,我们在共享库的代码中要包含它,javah不使默认内部命令,需要指明路径,它在JDK的bin目录下,在我的Linux环境下命令如下:

/home/jbuilder/jdk1.3.1/bin/javah Hello

生成的Hello.h 文件 内容如下所示:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Hello */
#ifndef _Included_Hello
#define _Included_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Hello
* Method: SayHello
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_Hello_SayHello
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif

2.在与Hello.h相同的路径下创建一个CPP文件Hello.cpp。内容如下:

#include "Hello.h"
#include <stdio.h>
//与Hello.h中函数声明相同
JNIEXPORT void JNICALL Java_Hello_SayHello (JNIEnv * env, jobject arg, jstring instring)
{
//从instring字符串取得指向字符串UTF编码的指针
const jbyte *str =
(const jbyte *)env->GetStringUTFChars( instring, JNI_FALSE );
printf("Hello,%s/n",str);
//通知虚拟机本地代码不再需要通过str访问Java字符串。
env->ReleaseStringUTFChars( instring, (const char *)str );
return;
}

所有的JNI调用都使用了JNIEnv *类型的指针,习惯上在CPP文件中将这个变量定义为evn,它是任意一个本地方法的第一个参数。env指针指向一个函数指针表,在VC中可以直接用"->"操作符访问其中的函数。

jobject 指向在此 Java 代码中实例化的 Java 对象 LocalFunction的一个句柄,相当于this指针。

后续的参数就是本地调用中有Java程序传进的参数,本例中只有一个String型参数。对于字符串型参数,因为在本地代码中不能直接读取 Java 字符串,而必须将其转换为 C /C++字符串或 Unicode。以下是三个我们经常会用到的字符串类型处理的函数:

const char* GetStringUTFChars(jstring string,jboolean* isCopy)

返回指向字符串UTF编码的指针,如果不能创建这个字符数组,返回null。这个指针在调用ReleaseStringUTFChar()函数之前一直有效。

参数:stringJava字符串对象
isCopy如果进行拷贝,指向以JNI_TRUE填充的jboolean,否则指向以JNI_FALSE填充的jboolean。
void ReleaseStringUTFChars(jstring str, const char* chars)
通知虚拟机本地代码不再需要通过chars访问Java字符串。

参数:stringJava字符串对象
chars由GetStringChars返回的指针
jstring NewStringUTF(const char *utf)
返回一个新的Java字符串并将utf内容拷贝入新串,如果不能创建字符串对象,
返回null。通常在反值类型为string型时用到。

参数:utfUTF编码的字符串指针
对于数值型参数,在C/C++中可直接使用,其字节宽度如下所示:
JavaC/C++字节数booleanjboolean1bytejbyte1charjchar2shortjshort2intjint4longjlong8floatjfloat4doublejdouble8

对于数组型参数,

JavaC/C++boolean[ ]JbooleanArraybyte[ ]JbyteArraychar[ ]JcharArrayshort[ ]JshortArrayint[ ]JintArraylong[ ]JlongArrayfloat[ ]JfloatArraydouble[ ]JdoubleArray对于上述类型数组,有一组函数与其对应。以下函数中Xxx为对应类型。
xxx * GetXxxArrayElements(xxxArray array, jboolean *isCopy)
产生一个指向Java数组元素的C指针。不再需要时,需将此指针传给ReleaseXxxArrayElemes。
参数:array数组对象
isCopy如果进行拷贝,指向以JNI_TRUE填充的jboolean,否则指向以JNI_FALSE填充的jboolean。
例如: jboolean * GetBooleanArrayElements(jbooleanArray array, jboolean *isCopy)
void ReleaseXxxArrayElements(xxxArray array,xxx *elems, jint mode)
通知虚拟机不再需要从GetXxxArrayElements得到的指针。
参数:array数组对象
elems不再需要的指向数组元素的指针
mode0=在更新数组元素后释放elems缓冲器
JNI_COMMIT=在更新数组元素后不释放elems缓冲器
JNI_ABORT=不更新数组元素释放elems缓冲器
例如:void ReleaseBooleanArrayElements(jbooleanArray array,jboolean *elems, jint mode)
xxxArray NewXxxArray(jsize len)
产生一个新的数组,通常在反值类型为数组型时用到
参数:len数组中元素的个数。
例如:jbooleanArray NewBooleanArray(jsize len)

3.编译生成共享库。

使用GCC时,必须通知编译器在何处查找此 Java 本地方法的支持文件,并且显式通知编译器生成位置无关的代码,在我的环境中按如下过程编译:

gcc -I/home/jbuilder/jdk1.3.1/include -I/home/jbuilder/jdk1.3.1/include/linux -fPIC -c Hello.c 

生成Hello.o

gcc -shared -Wl,-soname,libhello.so.1 -o libhello.so.1.0 Hello.o

生成libhello.so.1.0

接下来将生成的共享库拷贝为标准文件名

cp libhello.so.1.0 libhello.so

最后通知动态链接程序此共享文件的路径。

export LD_LIBRARY_PATH=`pwd`:$LD_LIBRARY_PATH

4.编写一个简单的Java程序来测试我们的本地方法。

将如下源码存为ToSay.java:

import Hello;
import java.util.*;
public class ToSay
{
public static void main(String argv[])
{
ToSay say = new ToSay();
}
public ToSay()
{
Hello h = new Hello();
//调用本地方法向John问好
h.SayHello("John");
}
}

用javac编译ToSay.java,生成ToSay.class
向执行普通Java程序一样使用java ToSay,我们会看到在屏幕上出现Hello,John。
到这里我们就将整个的本地调用编写过程大概浏览了一遍。





应用中注意事项

 

1.如果可以通过TCP/IP实现Java代码与本地C/C++代码的交互工作,那么最好不使用以上提到的JNI的方式,因为一次JNI调用非常耗时,大概要花0.5~1个毫秒。

2.在一个Applet应用中,不要使用JNI。因为在 applet 中可能引发安全异常。

3.将所有本地方法都封装在单个类中,这个类调用单个 DLL。对于每种目标操作系统,都可以用特定于适当平台的版本替换这个 DLL。这样就可以将本地代码的影响减至最小,并有助于将以后所需的移植问题包含在内。

4.本地方法要简单。尽量将生成的DLL 对任何第三方运行时 DLL 的依赖减到最小。使本地方法尽量独立,以将加载DLL 和应用程序所需的开销减到最小。如果必须要运行时 DLL,则应随应用程序一起提供它们。

5.本地代码运行时,没有有效地防数组越界错误、错误指针引用带来的间接错误等。所以必须保证保证本地代码的稳定性,因为,丝毫的错误都可能导致Java虚拟机崩溃。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 电脑剪切到u盘然后打不开了怎么办 淘宝未满十八岁限制购买物品怎么办 网上飞机订票手机号填写错了怎么办 室外回填土都是砂土压不实怎么办 王牌车新车储气筒漏气查不到怎么办 顺丰快递保价后商品出现问题怎么办 未保价快递丢失没有价值证明怎么办 安卓手机谷歌地图怎么用不了怎么办 ae模板版本太高打不开怎么办 苹果手机高德地图信号弱怎么办 网上订好火车票后没赶上火车怎么办 丰巢快递柜没收到短信怎么办 被不同号码骚扰电话打个不停怎么办 手机注册被骚扰电话打个不停怎么办 网贷不停的打骚扰电话怎么办 发改委的可研报告过期了怎么办 买到没有预售证的房子怎么办 网上买的学生票取不出来怎么办 买完学生票发现打折没次数了怎么办 动车晚点方向来反了怎么办 身份证购买高铁票过不了审核怎么办 手机购买高铁票身份核验失败怎么办 在高铁上如果有人占了座位该怎么办 网上买的高铁票改签怎么办 临沂村委会强行征收我的土地怎么办 学籍验证码连续输入三次错误怎么办 社保在上海个税在外地居转户怎么办 父母已经有英国签证孩子的怎么办 营运车辆被撞不肯赔务工费怎么办 欧米茄外壳上装表带的孔穿了怎么办 淘宝上买了电子产品坏了怎么办 研究生人才补助申请期限过了怎么办 一个小孩亲妈愿意养躲起来怎么办 网银转账名字打错了怎么办 百世快递邮东西到长春件丢失怎么办 三色吸顶灯有一色不亮了怎么办 超级试驾车超出取车行政区域怎么办 护士电子化注册激活码搞丢了怎么办 香港地铁地铁错买了特惠票怎么办 湖北软考证书领取没有准考证怎么办 哈罗单车电动车骑着没电了怎么办