从 Java 调用 c 函数
Posted
技术标签:
【中文标题】从 Java 调用 c 函数【英文标题】:Call c function from Java 【发布时间】:2011-05-11 11:11:29 【问题描述】:如何。 似乎 c 是基于编译器的。
我想在 Windows 中从 Java 调用 C 函数,并且 GCC 函数也来自 Java。
有参考吗?
【问题讨论】:
你可能想看看JNI(Java Native Interface)。 2011 年 5 月 11 日 11:11 提问 :-) 【参考方案1】:看看Java Native Interface: Getting Started。
2.1 概述
[...] 编写一个调用 C 函数进行打印的简单 Java 应用程序 “你好世界!”。该过程包括以下步骤:
创建一个声明本机方法的类 (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 实现编译为本机库,创建Hello-World.dll
或libHello-World.so
。使用 C 编译器和链接器 在宿主环境中可用。运行 HelloWorld 程序,使用 java运行时解释器。两个类文件 (HelloWorld.class
) 并加载本机库(HelloWorld.dll
或libHelloWorld.so
) 在运行时。本章的其余部分解释了这些步骤 详细。2.2 声明本机方法
您首先在 Java 编程中编写以下程序 语言。该程序定义了一个名为 HelloWorld 的类,其中包含一个 本机方法,打印。
class HelloWorld private native void print(); public static void main(String[] args) new HelloWorld().print(); static System.loadLibrary("HelloWorld");
HelloWorld 类定义以 print 本机方法的声明开始。接下来是一个主要方法 实例化 Hello-World 类并调用 print 本机方法 对于这个例子。类定义的最后一部分是静态的 加载包含 打印本机方法的实现。
native 方法的声明有两个区别 比如 print 和 Java 中常规方法的声明 编程语言。本机方法声明必须包含 本机修饰符。 native修饰符表示这个方法是 用另一种语言实现。此外,本机方法声明 以分号结尾,即语句终止符, 因为类中没有本地方法的实现 本身。我们将在单独的 C 文件中实现 print 方法。
在调用本地方法 print 之前,本地库 必须加载工具打印。在这种情况下,我们加载原生
HelloWorld
类的静态初始化程序中的库。爪哇 虚拟机在之前自动运行静态初始化程序 调用HelloWorld
类中的任何方法,从而确保 在调用 print 本机方法之前加载本机库。我们定义了一个能够运行
HelloWorld
类的main 方法。Hello-World.main
调用本机方法 print 的方式与 它会调用一个常规方法。
System.loadLibrary
采用库名称,定位一个本地库 对应于该名称,并将本机库加载到 应用。我们将在后面讨论确切的加载过程 书。现在只需记住,为了System.loadLibrary("HelloWorld")
要成功,我们需要创建一个 本机库在 Win32 上称为HelloWorld.dll
,或在 Win32 上称为libHelloWorld.so
索拉里斯。2.3 编译HelloWorld类
定义 HelloWorld 类后,将源代码保存在 名为 HelloWorld.java 的文件。然后使用编译源文件 JDK 或 Java 2 SDK 版本附带的 javac 编译器:
javac HelloWorld.java
这个命令会生成一个
HelloWorld.class
当前目录下的文件。2.4 创建原生方法头文件
接下来我们将使用
javah
工具生成JNI风格的头文件 这在用 C 实现本机方法时很有用。您可以运行javah
上的Hello-World
类如下: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
对象本身(有点像“this
” C++ 中的指针)。我们将讨论如何使用JNIEnv
接口 指针和本书后面的jobject
参数,但这个简单 示例忽略这两个参数。2.5 编写本机方法实现
javah
生成的 JNI 风格的头文件帮助你编写 C 或 本机方法的 C++ 实现。你写的函数 必须遵循生成的头文件中指定的 -prototype。你 可以在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
-- 代码 上面的 sn-p 还包括stdio.h
,因为它使用了printf
功能。HelloWorld.h
-- 您使用生成的头文件javah
。它包括Java_HelloWorld_print
的 C/C++ 原型 功能。 2.6 编译C源码并创建Native库请记住,当您在
HelloWorld.java
文件,您包含了一行代码,该代码加载了本机 库进入程序:System.loadLibrary("HelloWorld");
现在所有必要的 C 代码 写好了,需要编译
Hello-World.c
,构建这个native 图书馆。不同的操作系统支持不同的方式构建原生 图书馆。在 Solaris 上,以下命令构建一个共享库 调用 libHello-World.so:
cc -G -I/java/include -I/java/include/solaris HelloWorld.c -o libHelloWorld.so
-G 选项指示 C 编译器生成共享库而不是常规 Solaris 可执行文件。由于本书页宽的限制, 我们将命令行分成两行。你需要输入命令 在一行中,或将命令放在脚本文件中。在
Win32
上, 以下命令构建动态链接库 (DLL)HelloWorld.dll
使用 Microsoft Visual C++ 编译器: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
在当前 目录,或在 PATH 环境中列出的目录中 变量。在 Java 2 SDK 1.2 版本中,您还可以指定原生库 java命令行中的路径作为系统属性如下:
java -Djava.library.path=. HelloWorld
“
-D
”命令行选项 设置 Java 平台系统属性。设置java.library.path
".
" 的属性指示 Java 虚拟机搜索 当前目录中的本地库。
【讨论】:
Mac 用户可以忽略 solaris 命令。这个博客帮助我完成了这个答案帮助开始的事情:nerdposts.blogspot.com/2010/10/jni-mac-os-x-simple-sample.html 人们在阅读本文并沮丧为什么 javah 无法正常工作并告诉你Error: Could not find class file for 'YourFileName.class'.
***.com/questions/19137201/… 时也会派上用场
对于 Java >= 9 javah 已弃用,因此请使用 javac -h . HelloWorld.java
。到处都回答了这个问题,但我只是为了 tl;dr. 在这里添加它。【参考方案2】:
简单来说,只要确保你加载了包含函数定义的相关库,加载遵循 JNI 规范的库并从第一个库中包装目标函数,从你的 Java 类中公开本地方法,你应该是很好。
我建议不要使用原始 JNI,因为它包含大量样板代码,如果你开始包装一个 big C 库,你最终会自责。无论如何,在开始时请随意涉足 JNI,但在实际工作中使用 JNA 之类的东西。
【讨论】:
JNA 和 JNI 一样快吗?【参考方案3】:您的选择包括:
Java 原生接口 见:https://en.wikipedia.org/wiki/Java_Native_Interface
引用:
JNI 使程序员能够编写本地方法来处理无法完全用 Java 编程语言编写应用程序的情况,例如当标准 Java 类库不支持特定于平台的功能或程序库时
Java 原生访问
见:https://en.wikipedia.org/wiki/Java_Native_Access
引用:
Java Native Access 是一个社区开发的库,它使 Java 程序无需使用 Java Native 接口即可轻松访问本机共享库。
JNR-FFI
见:https://github.com/jnr/jnr-ffi
引用:
jnr-ffi 是一个 java 库,用于加载原生库,无需手动编写 JNI 代码,也无需使用 SWIG 等工具。
【讨论】:
那么 JNR diff JNA 是什么?【参考方案4】:在“exotic”类别中,请参阅 NestedVM,它将 C 编译为 Mips,并运行 Mips JVM 中的虚拟机。
http://nestedvm.ibex.org/
【讨论】:
【参考方案5】:结帐 JNAerator。 https://code.google.com/p/jnaerator/
您需要提供源代码和预处理器定义等。
【讨论】:
【参考方案6】:如果您使用的是 Windows 和 MinGW gcc,如果您在 lib 中遇到特定方法的 UnsatisfiedLinkError,则可能需要额外的标志:
gcc -D_JNI_IMPLEMENTATION_ -Wl,--kill-at -I"%JAVA_HOME%"\include -I"%JAVA_HOME%"\include\win32 BestCode.c -shared -o BestCode.dll
【讨论】:
【参考方案7】:@Jonas 给出了一个非常详尽的答案,但我认为它也非常值得检查这个网站,你会在那里得到所有重要的答案:
http://www.ntu.edu.sg/home/ehchua/programming/java/javanativeinterface.html
它解释了如何使用 JNI 调用程序:
对于语言 C 和 C++ 或两者的混合 没有任何参数、原语、字符串或原语数组的 JNI 访问对象变量、回调实例方法等等。【讨论】:
网页已关闭,新链接为personal.ntu.edu.sg/ehchua/programming/java/…【参考方案8】:我找到了解决这个问题的方法。您需要确保使用 64 位 c++ 编译器编译代码,以调用在 64 位 JRE 上运行的 java 函数。除此之外,我们还需要将创建的dll文件的路径保存在“环境变量”下的“路径”中。
【讨论】:
【参考方案9】:首先通过在属性java.library.path
设置路径来确保在类路径中加载您的本机库或.dll文件
然后使用System.loadLibrary()
Do not use .dll extension at the end.
【讨论】:
【参考方案10】:用于制作 64 位兼容的 dll 从下面的语句中删除“-MD”选项
cl -Ic:\java\include -Ic:\java\include\win32 -MD -LD HelloWorld.c -FeHelloWorld.dll
【讨论】:
以上是关于从 Java 调用 c 函数的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 JNI 从 JAVA 调用带有 C++ 参数的函数?