从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.dlllibHello-World.so 。 使用主机环境中可用的C编译器和链接器。 使用java运行时解释器运行HelloWorld程序。 在运行时加载类文件( HelloWorld.class )和本机库( HelloWorld.dlllibHelloWorld.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); 

现在忽略JNIEXPORTJNICALLmacros。 您可能已经注意到,本地方法的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.dllWin32multithreadingC库链接。 -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。

http://nestedvm.ibex.org/

如果你正在使用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.