项目需要java调用C函数,网上查了一些资料,调试成功,现将资料和过程记录下来。
平台介绍
系统:ubuntu10.04
jdk:Java(TM) SE Runtime Environment (build 1.6.0_20-b02)
gcc:gcc version 4.4.3 (Ubuntu 4.4.3-4ubuntu5)
过程介绍(以下内容转自http://blog.csdn.net/xiaojianpitt/archive/2010/06/07/5652223.aspx):
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文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。
易错部分补充:
1 java 中 c语言函数的声明
public native static void greeting(); //就像是接口声明一样,不过有native!
2 编译 javac HelloNative.java ,然后使用 javah
javah HelloNative会自动产生c的头文件HelloNative.h3 生成的头文件 的 第一句子为
#include <jni.h>
但是gcc里面默认环境可不知道jni.h是什么东西,jni.h在jdk的$JAVA_HOME/include或者$JAVA_HOME/include/linux下面,可进去查看一下~
4 接下来就是根据HelloNative.h中声明的方法写C语言的实现,注意,自动生成的那个函数名字很长,并且 开头的 Java是大写的,大小写很致命,(最后我的程序在动态库已经加载好的情况下报错:java.lang.UnsatisfiedLinkError: HelloNative.greeting()V,就是因为c语言中的函数名字大小写写错,奇怪!编译不报错.....)
5 linux下编译生成动态库,注意不同环境的不一样~
gcc -fPIC -I jdk/include -I jdk/include/linux -shared -o libHelloNative.so HelloNative.c
在这里,我犯的错:a,不理解 -I jdk -I 是include,显示指定库的库的地址,自然后面的jdk是要用你的设计地址替换的,b, 着急的去网上搜索问题,没有注意的在linux下,动态链接库的名字 必须是 lib****.so,必须以lib开头!
6 编译生成了 libHelloNative.h之后,接下写一个test类,如 HelloNativeTest,
当然要调用System.loadLibrary("HelloNative");注意此时不要lib,也不要.so!;
调用执行 HelloNative.greeting();这个时候错误又来了:
java.lang.UnsatisfiedLinkError: no HelloNative in java.library.path。这个错误很经典,原因:是java找不到库路径~:
显然: libHelloNative.so放在当前路径 ".",只linux执行的时候却不知道在当前路径找。 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 ,但是设置到哪里呢? /etc/profile ? or /root/.bashrc 不知道...忘记了linux的加载顺序了~
3 可以单次执行时候指定library位置:
java -Djava.library.path=. HelloNativeTest
总结犯错:1不知道gcc编译时候指定库 2 不了解java.libray.path的特点,特别是砸linux下 3 c语言实现函数的时候拼写错误 4 排除问题不够理性,系统化,出现了烦躁情绪,导致效率低。 时刻明白,机器只是做你指定的事情,总是你自己出错了~~~
分享到:
相关推荐
总的来说,Java调用C语言编写的本地库需要理解JNI和JNA的工作原理,以及它们如何处理数据类型转换、参数传递和本地库的加载。通过这些技术,开发者可以充分利用C/C++的性能优势,同时享受Java的平台独立性和高级抽象...
Java通过JNI(Java Native Interface)调用... - 示例应用:演示如何从Java调用本地方法的示例程序。 通过理解并实践这个项目,你可以深入掌握Java通过JNI调用C语言函数库的方法,为开发混合型Java应用打下坚实基础。
6. **测试Java调用C函数** 在Java程序中,现在可以通过`callCFunction`调用C代码: ```java public class Main { public static void main(String[] args) { NativeDemo demo = new NativeDemo(); demo....
### Linux环境下实现Java调用Windows环境下的Matlab函数 #### 一、所需条件及说明 为了实现在Linux环境中通过Java程序来调用Windows系统下的Matlab函数,首先需要确保满足以下条件: 1. **Windows环境下**: - ...
在Linux系统下,JAVA程序通过JNA技术实现调用C语言编程输出的so库接口函数,从而实现java程序与c程序之间的交互。
Linux下使用Java调用Hikvision设备网络SDK使用指南涉及的知识点相当丰富,本指南主要围绕如何在Linux平台下调用海康威视提供的设备网络SDK进行功能调用,重点是利用Java语言实现摄像机的抓拍功能。以下是本指南的...
Java调用DLL函数是跨平台编程中的一种常见需求,特别是在Java与C/C++代码交互时。JNA(Java Native Access)是Java平台上的一个库,它允许Java代码直接调用本机库(如DLL文件)的函数,而无需编写JNI(Java Native ...
本资源详细介绍了如何在Linux环境中通过C语言使用JNI调用Java类的函数,以下是对这个主题的详细阐述。 首先,要进行C语言调用Java类,我们需要确保有Java Development Kit (JDK) 安装并正确配置。JDK包含了一系列...
#### 三、Java调用C语言的基本步骤 1. **定义Native Method** 在Java类中声明native方法,这些方法实际上是在本地代码中实现的。 ```java public class TestDLL { static { System.loadLibrary("goodluck"); }...
这个"linux-java调用c语言编译的so动态库-jni例子-简单计算器"是一个典型的示例,展示了如何利用JNI来创建一个Java应用程序,该程序能够调用C编写的动态链接库(.so文件)进行计算操作。 JNI是Java平台的一部分,它...
Java在Linux环境中调用SAP RFC接口涉及到的关键技术点包括Java与SAP的集成、Linux系统下的动态链接库(.so文件)以及SAP的RFC(远程功能调用)技术。这里将详细介绍这些知识点。 首先,SAP RFC是SAP提供的一种通信...
Java程序调用linux命令、脚本,支持程序在服务器上使用linux命令。工具类为:ProcessUtil,支持单个命令和批量命令执行函数,同时在工具类中添加了getFileLineNumByCmd函数支持通过linux命令获取文件行数的样例。
### JAVA调用C函数:深入理解JNI #### 引言 在软件开发领域,Java以其跨平台性、安全性以及丰富的类库支持而受到广泛欢迎。然而,在某些特定场景下,如需要高性能运算、直接访问硬件资源或利用已有C/C++库时,Java...
Java Native Access(JNA)是Java平台上的一个开源库,它允许Java代码直接调用操作系统提供的本地函数,而无需编写JNI(Java Native Interface)代码。JNI是Java与本机代码交互的标准方式,但它通常需要编写大量的C/...
### Java调用R语言的方法详解 #### JRI (Java/R Interface) **简介:** JRI是一种允许Java程序通过单线程方式与R进行交互的工具。它能够将R的动态库加载到Java环境中,并提供一系列Java API来实现R的功能。JRI支持...
在Java编程环境中,有时我们需要利用C语言编写的高效或特定功能的库,这时可以借助Java Native Interface (JNI) 来实现Java调用C的动态链接库。以下将详细讲解这一技术。 1. Java Native Interface (JNI) JNI是...
### Java调用MATLAB的实例知识点详解 #### 一、MATLAB函数的创建与封装 在本案例中,首先需要创建一个MATLAB函数`operation.m`来实现基本的数学运算功能,具体步骤如下: 1. **函数定义**: - 函数名为`...
在实际项目中,Java调用DLL通常用于利用已有的C/C++库,或者执行一些Java不擅长的任务,如硬件控制、图形处理等。然而,由于涉及到本地代码,这也会增加程序的复杂性和维护难度,因此在选择这种方法时需要权衡利弊。...
标题中的“JNI实现的DEMO Java调用VC编写的DLL,对应于Linux .SO”指的是一个使用Java Native Interface (JNI) 技术的示例,该示例展示了如何在Java程序中调用由Visual C++ (VC) 编译生成的动态链接库(DLL) 文件,...