如何从Java调用C函数。 看起来C是编译器类型的。
我想在Windows中从Java调用C函数,以及从Java调用GCC函数。
有任何参考资料吗?
请查看Java Native Interface: Getting Started。
2.1 概述
[...]编写一个简单的Java应用程序,调用C函数来打印“Hello World!”。该过程由以下步骤组成:
创建一个声明本地方法的类(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编译器和链接器将C实现编译为本地库,创建Hello-World.dll
或libHello-World.so
。使用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本地方法的声明开始。接下来是一个main方法,该方法实例化Hello-World类并调用此实例的print本地方法。 类定义的最后部分是一个静态初始化程序,它加载包含print本地方法实现的本地库。
在Java编程语言中,声明本机方法(例如print)与声明常规方法有两个不同之处。本机方法声明必须包含本机修饰符。本机修饰符表示该方法在另一种语言中实现。此外,本机方法声明以分号终止,即语句终止符号,因为类本身没有本机方法的实现。我们将在单独的C文件中实现print方法。
在调用print本地方法之前,必须加载实现print的本地库。在本例中,我们在HelloWorld类的静态初始化程序中加载本地库。Java虚拟机会在调用HelloWorld类中的任何方法之前自动运行静态初始化程序,从而确保在调用print本机方法之前加载本地库。
我们定义了一个main方法来运行HelloWorld类。 Hello-World.main以与调用常规方法相同的方式调用print本地方法。
System.loadLibrary接受一个库名称,定位与该名称对应的本地库,并将本地库加载到应用程序中。我们将在本书中讨论确切的加载过程。现在只需记住,为了使System.loadLibrary(“HelloWorld”)成功,我们需要在Win32上创建名为HelloWorld.dll的本地库,或者在Solaris上创建名为libHelloWorld.so的本地库。
2.3编译HelloWorld类
在定义了HelloWorld类之后,请将源代码保存在名为HelloWorld.java的文件中。然后使用随JDK或Java 2 SDK发行版一起提供的javac编译器编译源文件:
javac HelloWorld.java
这条命令将在当前目录生成一个HelloWorld.class
文件。
2.4 生成本地方法头文件
接下来,我们将使用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
宏。您可能已经注意到原生方法的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函数显示字符串“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源文件并创建本地库
请记住,在HelloWorld.java
文件中创建HelloWorld
类时,您包括了一行代码,将本地库加载到程序中:
System.loadLibrary("HelloWorld");
现在所有必要的C代码已经编写完成,你需要编译Hello-World.c
并构建这个本地库。
不同的操作系统支持不同的构建本地库的方法。在Solaris上,以下命令将构建一个名为libHello-World.so的共享库:
cc -G -I/java/include -I/java/include/solaris HelloWorld.c -o libHelloWorld.so
-G选项指示C编译器生成共享库而不是常规的Solaris可执行文件。由于本书页面宽度的限制,我们将命令行分成两行。您需要在单行中键入命令,或将命令放置在脚本文件中。在Win32
上,以下命令使用Microsoft Visual C++编译器构建动态链接库(DLL)HelloWorld.dll
:
cl -Ic:\java\include -Ic:\java\include\win32 -MD -LD HelloWorld.c -FeHelloWorld.dll
-MD
选项确保HelloWorld.dll
链接了Win32
多线程C库。
-LD
选项指示C编译器生成DLL而不是常规的Win32可执行文件。
当然,在Solaris和Win32上,您需要放入包含反映自己机器设置的路径。
2.7 运行程序
此时,你已经准备好运行程序的两个组件。
类文件(HelloWorld.class
)调用本地方法,
而本地库(Hello-World.dll
)实现了本地方法。
由于HelloWorld
类包含自己的main方法,因此可以在Solaris或Win32上如下运行程序:
java HelloWorld
您应该看到以下输出:
Hello World!
设置正确的本地库路径对于你的程序正常运行非常重要。本地库路径是Java虚拟机在加载本地库时搜索的目录列表。如果你没有正确设置本地库路径,那么你会看到类似以下错误:
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)
确保本地库位于本地库路径中的一个目录中。如果您正在 Solaris 系统上运行,则使用 LD_LIBRARY_PATH 环境变量来定义本地库路径。确保它包括包含 libHelloWorld.so 文件的目录的名称。如果 libHelloWorld.so 文件在当前目录中,您可以在标准 shell (sh) 或 KornShell (ksh) 中发出以下两个命令来正确设置 LD_LIBRARY_PATH 环境变量: LD_LIBRARY_PATH=.
export LD_LIBRARY_PATH
在C shell(csh或tcsh)中,等效的命令如下:
setenv LD_LIBRARY_PATH .
如果您正在运行Windows 95或Windows NT计算机,请确保HelloWorld.dll
在当前目录中,或者在路径环境变量中列出的目录中。
在Java 2 SDK 1.2版本中,您还可以通过以下系统属性在java命令行中指定本地库路径:
java -Djava.library.path=. HelloWorld
命令行选项"-D
"可设置Java平台系统属性。将java.library.path
属性设置为".
"会指示Java虚拟机在当前目录中搜索本地库文件。
javac -h . HelloWorld.java
。这个问题已经得到了广泛的回答,但为了方便阅读,我在这里再次提及。 - Juicestus简单来说,只需确保加载包含函数定义的相关库,加载符合JNI规范并将目标函数从第一个库封装的库,从您的Java类公开本机方法,这样就可以开始使用了。
我建议不要使用原始JNI,因为它包含大量样板代码,如果您开始封装一个大型的C库,您最终会感到非常恼怒。当然,在刚开始学习时可以尝试一下JNI,但真正做实际工作时可以使用像JNA这样的工具。
@Jonas 给出了非常详细的答案,但我认为也值得查看这个网站,你会在那里找到所有必要的答案:
http://www.ntu.edu.sg/home/ehchua/programming/java/javanativeinterface.html
本文介绍如何使用JNI调用程序:
gcc -D_JNI_IMPLEMENTATION_ -Wl,--kill-at -I"%JAVA_HOME%"\include -I"%JAVA_HOME%"\include\win32 BestCode.c -shared -o BestCode.dll
为了制作64位兼容的dll,请从下面的语句中删除“-MD”选项
cl -Ic:\java\include -Ic:\java\include\win32 -MD -LD HelloWorld.c -FeHelloWorld.dll
首先,请确保通过设置属性java.library.path
将本地库或.dll文件加载到类路径中。
然后使用System.loadLibrary()
。
Do not use .dll extension at the end.