Linux平台下Java调用C函数

JNI是Java native interface的简写,可以译作Java原生接口。Java可以通过JNI调用C/C++的库,这对于那些对性能要求比较高的Java程序无疑是一个 福音。

使用JNI也是有代价。大家都知道JAVA程序是运行在JVM之上的,可以做到平台无关。但是如果Java程序通过JNI调用了原生的代码(比如 c/c++等),则Java程序就丧失了平台无关性。最起码需要重新编译原生代码部分。所以应用JNI需要好好权衡,不到万不得已,请不要选择JNI,可 以选择替代方案,比如TCP/IP进行进程间通讯等等。这也是为什么谷歌的Android平台的底层虽然用JNI实现,但是他不建议开发人员用JNI来开 发Android上面的应用的原因。将会丧失Android上面的应用程序平台无关性。

下面是在linux下java jni调用C语言动态链接库的具体操作步骤。

1、创建一个Java程序(Hello.java)定义原生的c/c++函数。

2、用javac编译Hello.java生成Hello.class。

3、用javah带-jni参数编译Hello.class生成Hello.h文件,该文件中 定义了c的函数原型。在实现c函数的时候需要。

4、创建Hello.c,实现Hello.h定义的函数。

5、编译Hello.c生成libHello.so。

6、在java虚拟机运行java程序Hello。

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

此时应注意两点:

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

public native void SayHello(String strName);

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

2.必须显式地加载本地代码库。当然要调用System.loadLibrary("hello");注 意此时不要lib,也不要.so!; 我们需在类的一个静态块中加载这个库:

static

{

System.loadLibrary("hello");

}

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

public class Hello

{

static

{

System.loadLibrary("hello");

}

//声明的本地方法

public staitc native void sayHello(String strName);

}

运行命令 javac Hello.java 生成Hello.class文件。

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

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

javah Hello

但是出现如下错误:

error: cannot access Hello

class file for Hello not found

javadoc: error - Class Hello not found.

Error: No classes were specified on the command line. Try -help.

原因是CLASS_PATH没有把当前目录加入其中。所以必须指定classpath 为当前目录。或者在系统CLASS_PATH加入当前路径。执行如下命令:

javah -classpath . Hello

生成的Hello.h 文件内容的第一句子为 #include <jni.h>
但是gcc里面默认环境可不知道jni.h是什么东西,jni.h在jdk 的$JAVA_HOME/include下面,可进去查看一下~

2.在与Hello.h相同的路径下创建一个CPP文件Hello.cpp。注意,自动生成的那个函数名字很长,并且 开头的  Java是大写的,大小写很致命一定要注意。内容如下:

#include "Hello.h"

#include <stdio.h>

//与Hello.h中函数声明相同

JNIEXPORT void JNICALL Java_Hello_sayHello (JNIEnv * env, jclass 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文件中将这个变量定义为env,它是任意一个本地方法的第一个参数。env指针指向一个函数指针表,在VC中可以直接 用"->"操作符访问其中的函数。

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

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

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

3.编译生成共享库。

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

g++ -I /usr/lib/jvm/java-6-sun/include/linux/ -I /usr/lib/jvm/java-6-sun/include/ -fPIC -c Hello.cpp

生成Hello.o

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

生成libhello.so.1.0

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

cp libhello.so.1.0 libhello.so

或者使用:

g++ -I /usr/lib/jvm/java-6-sun/include/linux/ -I /usr/lib/jvm/java-6-sun/include/ -fPIC -shared -o libLexical.so Lexical.cpp
注意在linux下,动态链接库的名字 必须是 lib****.so,必须以lib开头!
4.编写一个简单的Java程序来测试我们的本地方法。

将如下源码存为ToSay.java:

public class ToSay

{

public static void main(String argv[])

{

Hello.sayHello("John");

}

}

用javac编译ToSay.java命令javac -cp . ToSay.java,生成ToSay.class。
向执行普通Java程序一样使用java ToSay,

java ToSay

Exception in thread "main" java.lang.NoClassDefFoundError: ToSay

Caused by: java.lang.ClassNotFoundException: ToSay

at java.net.URLClassLoader$1.run(URLClassLoader.java:202)

at java.security.AccessController.doPrivileged(Native Method)

at java.net.URLClassLoader.findClass(URLClassLoader.java:190)

at java.lang.ClassLoader.loadClass(ClassLoader.java:307)

at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)

at java.lang.ClassLoader.loadClass(ClassLoader.java:248)

Could not find the main class: ToSay. Program will exit.

原因是classpath没有当前路径。改正后执行如下命令:

java -cp . ToSay

Exception in thread "main" java.lang.UnsatisfiedLinkError: no hello in java.library.path

at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1734)

at java.lang.Runtime.loadLibrary0(Runtime.java:823)

at java.lang.System.loadLibrary(System.java:1028)

at Hello.<clinit>(Hello.java:6)

at ToSay.main(ToSay.java:5)

依然报错。java.lang.UnsatisfiedLinkError: no HelloNative in java.library.path。这个错误很经典,原因:是java找不到库路径~: 显然: libhello.so放在当前路径 ".",只linux执行的时候却不知道在当前路径找。
a. linux下面java.library.path 和环境变脸 jdk/bin的那个个PATH不是一回事情,有另外一个默认变量 LD_LIBRARY_PATH来保存他的信息。而windows下,首先java会找当前目录,其次,它会去环境变量的地址找!
b。 由于linux的路径特殊,所以,解决方法 1-可以调用sysout(System.getProperty("java.library.path")); 来查看! 然后把 libXXXX.so拷贝到那里面的目录下去
2 设置环境变量 export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
3 可以单次执行时候指定library位置:
  java -Djava.library.path=. -cp . ToSay

我们会看到在屏幕上出现Hello,John。
附:gcc 参数解释(转载):
最主要的是GCC命令行的一个选项:
-shared 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件
l -fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真 正代码段共享的目的。
l -L.:表示要连接的库在当前目录中
l -ltest:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称
l LD_LIBRARY_PATH:这个环境变量指示动态连接器可以装载动态库的路径。
l 当然如果有root权限的话,可以修改/etc/ld.so.conf文件,然后调用 /sbin/ldconfig来达到同样的目的,不过如果没有root权限,那么只能采用输出LD_LIBRARY_PATH的方法了。
4、注意
调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录 通过 “-I” include进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过ldd命令察看时,就是死活找不到你指定链接的so文件,这时你要作的就是通过修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。

时间: 2024-10-04 11:23:46

Linux平台下Java调用C函数的相关文章

Linux平台下Java_Android开发环境的搭建

Linux 平台下安装JDK .Eclipse .Android SDK 说明 开发工具需自行去下载,此处就不再累述 该教程安装环境为 Ubuntu 14.04 x64 其他Linux操作方式基本相同 设计文件修改部分,请先备份要修改的文件,避免操作失误导致不能还原 Liunx 平台下区分大小写,输入文件名或路径建议复制避免不必要的错误 部分操作涉及root权限,为了避免麻烦 请直接使用 root权限操作 开启root权限: 1. sudo su root 2. 后面的提示中输入用户密码 安装

浅谈Windows平台下C++调用静态链接库的方式

浅谈Windows平台下C++调用静态链接库的方式 1. 什么是静态链接库?为什么要用静态链接库? 维基百科上关于静态库的解释是这样的:在计算机科学里,静态库(英语:Static library, Statically-linked library),或称静态库,是一个外部函数与变量的集合体.静态库的文件内容,通常包含一堆程序员自定的变量与函数,其内容不像动态链接库那么复杂,在编译期间由编译器与连接器将它集成至应用程序内,并制作成目标文件以及可以独立运作的可执行文件. 由上面的解释可以很清楚的看

Linux平台下Python的安装及IDE开发环境搭建

Linux平台下Python的安装及IDE开发环境搭建 1.Python安装 Python有2.X和3.X两个版本,由于2.X的版本较稳定,使用者也较多,本文选择使用Python 2.X版本. 安装步骤: (1) 下载Python安装包:https://www.python.org/downloads/ (2)  解压安装包:tar zxvf Python-2.7.10.tgz (3)  编译:./compile (4)  安装:make && make install 说明: ① 这样p

Android使用JNI(从java调用本地函数)

当编写一个混合有本地C代码和Java的应用程序时,需要使用Java本地接口(JNI)作为连接桥梁.JNI作为一个软件层和API,允许使用本地代码调用Java对象的方法,同时也允许在Java方法中调用本地函数. 在Java端,开发者所需要做的仅仅是在连接本地函数的方法之前加上native关键字.这样VM就会去寻找这个本地函数. 1.从Java调用本地函数 从Java调用本地函数时,需要在类中定义一个带有native关键字的特有方法,作为连接本地代码的桥梁.通过这个定义,尝试调用本地方法时JVM会找

在linux平台下,设置core dump文件属性(位置,大小,文件名等)

在linux平台下,设置core dump文件生成的方法: 1) 在终端中输入ulimit -c 如果结果为0,说明当程序崩溃时,系统并不能生成core dump. 2) 使用ulimit -c unlimited命令,开启core dump功能,并且不限制生成core dump文件的大小.如果需要限制,加数字限制即可.ulimit - c 1024 3) 默认情况下,core dump生成的文件名为core,而且就在程序当前目录下.新的core会覆盖已存在的core.通过修改/proc/sys

JAVA调用JS函数

今天在开发中使用到了js和Java的交互,平时我们用的比较多的就是js调用Java的方法,可以使用dwr.Ajax.jquery.突然发现要Java调用js的话还真没有见过,今天上网查询了下资料,顺便总结一下: 前提条件:jdk1.6才支持,1.6以前的就不行了. js代码如下: function convert (id,str) { if ('505041'==id) { return str; } else { return "A"; } } Java代码如下: package c

Linux平台下:块设备、裸设备、ASMlib、Udev相关关系

对磁盘设备(裸分区)的访问方式分为两种:1.字符方式访问(裸设备):2.块方式访问 Solaris平台 : 在Solaris平台下,系统同时提供对磁盘设备的字符.块方式访问.每个磁盘有两个设备文件名: 一个在/dev/dsk目录下,比如/dev/dsk/c1t1d1s1,当以这个设备名操作时,就是以块的方式操作磁盘: 一个在/dev/rdsk目录下,比如/dev/rdsk/c1t1d1s1,当以这个设备名操作时,就是以字符方式(裸设备方式r)操作磁盘 Linux平台 : 在linux平台下,缺省

关于Linux平台下的ZFS文件系统最新情况

Linux平台下的ZFS文件系统分类两个,一个是在用户空间实现的ZFS,一个是通过内核模块实现的ZFS. 用户空间实现的ZFS已经好几年没人维护了,且不说稳定性,单是性能就无法用,相关开发人员已放弃. 内核空间实现的ZFS一直在维护,美国有相关机构在内部使用,按照zfsonlinux上的开发者所说,早已经稳定了,可以在线上使用,但国内使用的人还是寥寥.可能最大的问题就是,由于Solaris自身发布协议和专利纠纷的限制,此项目的代码仍是基于CDDL发布的,因而不会进入主线内核,所以大家普遍感觉不是

linux平台下Oracle database的安装与/etc/redhat-release

linux平台下,在安装oracle10.2.0.1的时候,不要修改/etc/redhat-release的内容. 而是要用runInstaller的一个参数去解决:-ignoreSysPreReqs.因为emc的多路径软件在安装时会读取该文件的内容,错误的内容会导致emc多路径软件安装失败.这也算是一个最佳实践吧. 版权声明:本文为博主原创文章,未经博主允许不得转载.