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

暂时忽略JNIEXPORTJNICALL 宏。你可能已经注意到 本机方法的 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.dllWin32 多线程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 函数的主要内容,如果未能解决你的问题,请参考以下文章

JAVA如何调用C函数

java调用C 函数

如何使用 JNI 从 JAVA 调用带有 C++ 参数的函数?

从 java 代码调用外部 javascript 函数

Android在C ++ JNI代码中从另一个活动类调用Java函数

(JNI)从cpp lib调用函数时的数据类型转换