2011-05-11 127 views
49

如何从Java调用c函数。 似乎c是基于编译器的。从Java调用c函数

我想从Java调用Windows的C函数,也可以调用Java的GCC函数 。

有没有参考?

+0

你可能想看看JNI(Java本地接口)。 – 2011-05-11 11:13:21

+1

我会从这些http://www.google.co.uk/search?q=JNI+tutorial – 2011-05-11 11:14:07

+0

开始。非常感谢。 – Wen 2011-05-11 13:53:09

回答

54

看看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实现编译为本地库,从而创建 Hello-World.dlllibHello-World.so。使用主机环境中可用的C编译器和链接器 。运行HelloWorld程序,使用java运行时解释器 。类文件(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)的声明与Java 编程语言中常规方法的声明之间有两点区别。本机方法声明必须包含 本机修饰符。本地修饰符表示此方法是以另一种语言实现的 。此外,原生方法声明 以分号(语句结束符符号 )结尾,因为 类本身没有针对本地方法的实现。我们将在一个单独的C文件中实现打印方法。

在调用本地方法打印之前,必须加载 实现打印的本地库。在这种情况下,我们在HelloWorld类的静态初始化程序中加载本地 库。在 调用HelloWorld类中的任何方法之前,Java 虚拟机自动运行静态初始化程序,从而确保在调用打印本机方法之前加载了 本机库。

我们定义一个能够运行HelloWorld类的主要方法。 Hello-World.main调用本地方法打印的方式与调用常规方法的 相同。

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,这 是C函数实现该方法HelloWorld.print:

JNIEXPORT void JNICALL Java_HelloWorld_print (JNIEnv *, jobject); 

忽略JNIEXPORTJNICALL宏现在。您可能已注意到 即使本机方法的相应声明接受 无参数,本机方法的C实现也接受两个参数 。每种本地方法 实现的第一个参数是一个JNIEnv接口指针。第二个参数是对HelloWorld对象本身的引用 (有点像C++中的“this” 指针)。我们将在本书稍后讨论如何使用JNIEnv接口 指针和jobject参数,但是这个简单的 示例忽略了这两个参数。

2.5编写本地方法实现

通过javah产生的JNI风格的头文件可以帮助你编写C或本地方法 C++实现。您编写的函数 必须遵循生成的头文件中指定的原型。您 可以实现在C文件中HelloWorld.cHello-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,将 以下命令生成一个动态链接库(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类包含自己的主要方法,您可以按以下运行 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文件是在当前 目录,你可以发出在标准 壳(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是在T他目前在 目录下,或者在PATH环境下列出的目录 变量中。

在Java 2 SDK中1。2版本中,你也可以指定Java命令行上的本地库 路径为系统属性如下:

java -Djava.library.path=. HelloWorld 

的“-D”命令行选项 设置一个Java平台的系统性能。将java.library.path 属性设置为“.”会指示Java虚拟机在当前目录中搜索 本机库。

+0

Mac用户可以忽略solaris命令。这个博客帮助我完成了这个答案帮助开始的东西:http://nerdposts.blogspot.com/2010/10/jni-mac-os-x-simple-sample.html – cloudsurfin 2015-07-15 01:31:34

11

简而言之,只要确保加载了包含函数定义的相关库,加载遵循JNI规范的库并包装来自第一个库的目标函数,公开Java类中的本地方法以及你应该很好走。

我建议对生JNI,因为它包含了很多样板代码,如果你开始打包 C库,你最终会诅咒自己。尽一切可能在开始时随意涉足JNI,但在实际工作中使用类似JNA的东西。

+0

JNA和JNI一样快吗? – Pacerier 2017-06-17 22:01:59

0

JNI - Java本地接口

为了从Java调用C函数,你需要使用JNI

3

您的选项包括:

Java本地接口
见:https://en.wikipedia.org/wiki/Java_Native_Interface

报价:

JNI允许程序员编写本地方法来处理情况当应用程序不能完全用Java编程语言编写时,例如当标准Java类库不支持特定于平台的功能或程序库

Java本机访问

见:https://en.wikipedia.org/wiki/Java_Native_Access

报价:

Java本机Access是一个社区开发的库,它可以让Java程序轻松访问本地共享库,而无需使用Java Nat ive接口。

JNR-FFI

见:https://github.com/jnr/jnr-ffi

报价:

JNR-FFI是加载本地库一个Java库,而无需手动编写JNI代码,或使用诸如SWIG之类的工具。

+0

所以JNR diff JNA是? – Pacerier 2017-06-17 21:55:32

0

对于低于

“CL -Ic使兼容64位的DLL删除 “-MD” 从语句选项:\ java的\包括-Ic:\ java \ include \ win32 -MD -LD HelloWorld.c -FeHelloWorld.dll“

0

我得到了解决这个问题的方法。您需要确保的是,您正在使用64位C++编译器编译代码,以调用在64位JRE上运行的Java函数。除此之外,我们需要将创建的dll文件的路径保存在“环境变量”下的“路径”中。

1

如果您使用的是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 
0

首先要确保加载您的本机库或.dll文件在类路径通过产权java.library.path设置路径

然后使用System.loadLibrary()

Do not use .dll extension at the end.