从Java调用c函数
如何从Java调用c函数 看来c是基于编译器的。
我想从Java调用Windows中的C函数,以及Java中的GCC函数。
任何参考?
查看Java Native Interface:入门 。
2.1概述
编写一个简单的Java应用程序,调用C函数来打印“Hello World!”。 该过程由以下步骤组成:
创build一个声明本地方法的类(HelloWorld.java)。 使用javac编译HelloWorld源文件,生成类文件HelloWorld.class。 javac编译器随JDK或Java 2 SDK发行版提供。 使用
javah -jni
生成包含本地方法实现的函数原型的C头文件(HelloWorld.h
)。 javah工具随JDK或Java 2 SDK版本一起提供。 编写本地方法的C实现(HelloWorld.c
)。 将C实现编译为本地库,创buildHello-World.dll
或libHello-World.so
。 使用主机环境中可用的C编译器和链接器。 使用java运行时解释器运行HelloWorld程序。 在运行时加载类文件(HelloWorld.class
)和本机库(HelloWorld.dll
或libHelloWorld.so
)。 本章的其余部分将详细解释这些步骤。2.2声明本地方法
你首先用Java编程语言编写下面的程序。 程序定义了一个名为HelloWorld的类,它包含一个本地方法print。
class HelloWorld { private native void print(); public static void main(String[] args) { new HelloWorld().print(); } static { System.loadLibrary("HelloWorld"); } }
HelloWorld类定义从print native方法的声明开始。 接着是实例化Hello-World类的主要方法,并调用此实例的打印本地方法。 类定义的最后一部分是一个静态初始化程序,用于加载包含print本地方法实现的本地库。
本地方法(如print)的声明与Java编程语言中常规方法的声明之间有两个区别。 本地方法声明必须包含本地修饰符。 本地修饰符指示此方法以其他语言实现。 另外,本地方法声明以分号(语句结束符)结束,因为类本身没有本地方法的实现。 我们将在一个单独的C文件中实现打印方法。
在调用本地方法打印之前,必须加载实现打印的本机库。 在这种情况下,我们将本地库加载到
HelloWorld
类的静态初始化程序中。 Java虚拟机在调用HelloWorld
类中的任何方法之前自动运行静态初始化程序,从而确保在调用print本地方法之前加载本地库。我们定义一个能够运行
HelloWorld
类的主要方法。Hello-World.main
以与调用常规方法相同的方式调用本地方法打印。
System.loadLibrary
采用库名称,find与该名称对应的本地库,并将本地库加载到应用程序中。 我们将在本书后面讨论确切的加载过程。 现在简单地记住为了使System.loadLibrary("HelloWorld")
成功,我们需要在Win32上创build一个名为HelloWorld.dll
的本地库,或者在Solaris上创build一个名为libHelloWorld.so
的本地库。2.3编译HelloWorld类
在定义了HelloWorld类之后,将源代码保存在名为HelloWorld.java的文件中。 然后使用JDK或Java 2 SDK发行版附带的javac编译器编译源文件:
javac HelloWorld.java
这个命令会在当前目录下生成一个
HelloWorld.class
文件。2.4创build本地方法头文件
接下来我们将使用
javah
工具来生成一个JNI风格的头文件,这在用C实现本地方法时非常有用。您可以在Hello-World
类上运行javah
,如下所示:javah -jni HelloWorld
头文件的名称是在其末尾附加“
.h
”的类名。 上面显示的命令生成一个名为HelloWorld.h
的文件。 我们不会在这里列出生成的头文件。 头文件最重要的部分是Java_HelloWorld_print
的函数原型,它是实现HelloWorld.print方法的C函数:JNIEXPORT void JNICALL Java_HelloWorld_print (JNIEnv *, jobject);
现在忽略
JNIEXPORT
和JNICALL
macros。 您可能已经注意到,本地方法的C实现接受两个参数,即使本地方法的相应声明不接受任何参数。 每个本地方法实现的第一个参数是一个JNIEnv
接口指针。 第二个参数是对HelloWorld
对象本身的引用(有点像C ++中的“this
”指针)。 本书稍后将讨论如何使用JNIEnv
接口指针和jobject
参数,但是这个简单的例子忽略了这两个参数。2.5编写原生方法实现
由
javah
生成的JNI风格的头文件可以帮助您为本地方法编写C或C ++实现。 您编写的函数必须遵循生成的头文件中指定的原型。 您可以在C文件HelloWorld.c
实现Hello-World.print
方法,如下所示:#include <jni.h> #include <stdio.h> #include "HelloWorld.h" JNIEXPORT void JNICALL Java_HelloWorld_print(JNIEnv *env, jobject obj) { printf("Hello World!\n"); return; }
这个本地方法的实现很简单。 它使用printf函数来显示string“Hello World!” 然后返回。 如前所述,两个参数,
JNIEnv
指针和对象的引用都被忽略。C程序包含三个头文件:
jni.h
– 这个头文件提供了本地代码需要调用JNI函数的信息。 编写本地方法时,必须始终将此文件包含在C或C ++源文件中。stdio.h
– 上面的代码片段还包含了stdio.h
因为它使用了printf
函数。HelloWorld.h
– 使用javah
生成的头文件。 它包含Java_HelloWorld_print
函数的C / C ++原型。 2.6编译C源代码并创build一个本地库请记住,当您在
HelloWorld.java
文件中创buildHelloWorld
类时,您包含一行将本机库加载到程序中的代码:System.loadLibrary("HelloWorld");
现在已经编写了所有必要的C代码,您需要编译
Hello-World.c
并构build这个本地库。不同的操作系统支持不同的方法来构build本地库。 在Solaris上,以下命令构build一个名为libHello-World.so的共享库:
cc -G -I/java/include -I/java/include/solaris HelloWorld.c -o libHelloWorld.so
-G选项指示C编译器生成共享库,而不是常规的Solaris可执行文件。 由于本书的页宽限制,我们把命令行分成两行。 您需要在一行中input命令,或将该命令放在脚本文件中。 在
Win32
,以下命令使用Microsoft Visual C ++编译器生成dynamic链接库(DLL)HelloWorld.dll
:cl -Ic:\java\include -Ic:\java\include\win32 -MD -LD HelloWorld.c -FeHelloWorld.dll
-MD
选项确保HelloWorld.dll
与Win32
multithreadingC库链接。-LD
选项指示C编译器生成DLL而不是常规的Win32可执行文件。 当然,在Solaris和Win32上,您都需要放入包含自己机器上的安装path。2.7运行程序
在这一点上,你有两个组件准备运行该程序。 类文件(
HelloWorld.class
)调用本地方法,本地库(Hello-World.dll
)实现本地方法。由于
HelloWorld
类包含自己的主方法,因此可以在Solaris或Win32上运行该程序,如下所示:java HelloWorld
您应该看到以下输出:
Hello World!
为程序运行正确设置本机库path非常重要。 本地库path是Java虚拟机在加载本机库时search的目录列表。 如果您没有正确设置本机库path,则会看到与以下类似的错误:
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)
确保本机库驻留在本机库path的其中一个目录中。 如果您在Solaris系统上运行,则使用
LD_LIBRARY_PATH
环境variables来定义本机库path。 确保它包含包含libHelloWorld.so
文件的目录的名称。 如果libHelloWorld.so
文件位于当前目录中,则可以在标准shell(sh)或KornShell(ksh)中发出以下两个命令来正确设置LD_LIBRARY_PATH
环境variables:LD_LIBRARY_PATH=. export LD_LIBRARY_PATH
C shell(csh或tcsh)中的等效命令如下所示:
setenv LD_LIBRARY_PATH .
如果您在Windows 95或Windows NT计算机上运行,请确保
HelloWorld.dll
位于当前目录或PATH环境variables中列出的目录中。在Java 2 SDK 1.2版本中,您还可以在java命令行上将系统属性指定为系统属性,如下所示:
java -Djava.library.path=. HelloWorld
“
-D
”命令行选项设置Java平台系统属性。 将java.library.path
属性设置为“.
”会指示Java虚拟机在当前目录中search本机库。
简而言之,只要确保加载了包含函数定义的相关库,加载遵循JNI规范的库并包装来自第一个库的目标函数,从Java类暴露本地方法,并且应该很好。
我build议对生JNI,因为它包含了很多样板代码,如果你开始打包一个大的 C库,你最终会诅咒自己。 不pipe怎样,在开始的时候都可以自由地涉足JNI,但是在实际工作中使用JNA这样的东西。
您的select包括:
Java本地接口
请参阅: https : //en.wikipedia.org/wiki/Java_Native_Interface
引用:
JNI使编程人员能够编写本地方法来处理应用程序不能完全用Java编程语言编写的情况,例如,当标准Java类库不支持特定于平台的function或程序库时
Java本地访问
请参阅: https : //en.wikipedia.org/wiki/Java_Native_Access
引用:
Java本地访问是一个社区开发的库,它提供Java程序轻松访问本地共享库而无需使用Java本地接口。
JNR-FFI
请参阅: https : //github.com/jnr/jnr-ffi
引用:
jnr-ffi是一个用于加载本地库而不用手工编写JNI代码或者使用诸如SWIG之类的工具的java库。
在“异类”类别中,请参阅NestedVM,它将C编译为Mips,并在JVM内部运行Mips VM。
如果你正在使用Windows和MinGW gcc,你可能需要额外的标志,如果你正在得到UnsatisfiedLinkError在lib中的具体方法:
gcc -D_JNI_IMPLEMENTATION_ -Wl,--kill-at -I"%JAVA_HOME%"\include -I"%JAVA_HOME%"\include\win32 BestCode.c -shared -o BestCode.dll
JNI – Java本地接口
为了从Java调用C函数,你需要使用JNI
结帐JNAerator。 https://code.google.com/p/jnaerator/
您需要提供源代码和预处理器定义等。
为了使64位兼容的DLL从下面的语句中删除“-MD”选项
“cl -Ic:\ java \ include -Ic:\ java \ include \ win32 -MD -LD HelloWorld.c -FeHelloWorld.dll”
我得到了这个问题的解决scheme。 您需要确保的是,您正在使用64位C ++编译器编译代码,以调用在64位JRE上运行的Java函数。 随着它,我们需要保存在“环境variables”下的“path”创build的dll文件的path。
首先确保通过在属性java.library.path
设置path来在类path中加载本机库或.dll文件
然后使用System.loadLibrary()
Do not use .dll extension at the end.