从Java调用c函数

Ali*_*wan 61 c java

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

我想从Java调用Windows中的C函数,也从Java调用GCC函数.

任何参考?

Jon*_*nas 72

看看Java Native Interface:入门.

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发行版一起提供.编写本HelloWorld.c机方法的C实现().将C实现编译到本机库中,创建 Hello-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");
    }
}
Run Code Online (Sandbox Code Playgroud)

HelloWorld类定义以print native方法的声明开头.接下来是一个main方法,它实例化Hello-World类并为此实例调用print native方法.类定义的最后一部分是一个静态初始化程序,它加载包含print native方法实现的本机库.

原始方法(如print)的声明与Java编程语言中常规方法的声明之间存在两个区别.本机方法声明必须包含本机修饰符.native修饰符表示此方法是用另一种语言实现的.此外,本机方法声明以分号(语句终止符号)结束,因为类本身没有本机方法的实现.我们将在单独的C文件中实现print方法.

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

我们定义了一个能够运行HelloWorld该类的main方法. Hello-World.main以与调用常规方法相同的方式调用本机方法print.

System.loadLibrary获取库名称,找到与该名称对应的本机库,并将本机库加载到应用程序中.我们将在本书后面讨论确切的加载过程.现在只需记住,为了 System.loadLibrary("HelloWorld")成功,我们需要创建一个HelloWorld.dll在Win32或libHelloWorld.soSolaris 上调用的本机库.

2.3编译HelloWorld类

定义HelloWorld类之后,将源代码保存在名为HelloWorld.java的文件中.然后使用JDK或Java 2 SDK版本附带的javac编译器编译源文件:

 javac HelloWorld.java
Run Code Online (Sandbox Code Playgroud)

此命令将HelloWorld.class 在当前目录中生成一个文件.

2.4创建本机方法头文件

接下来,我们将使用该javah工具生成JNI样式的头文件,该文件在C中实现本机方法时非常有用.您可以按如下方式javahHello-World类上运行 :

  javah -jni HelloWorld
Run Code Online (Sandbox Code Playgroud)

头文件的名称是类名,.h在其末尾附加了" ".上面显示的命令生成一个名为的文件HelloWorld.h.我们不会在此完整列出生成的头文件.头文件最重要的部分是函数原型Java_HelloWorld_print,它是实现HelloWorld.print方法的C函数:

 JNIEXPORT void JNICALL   Java_HelloWorld_print (JNIEnv *, jobject);
Run Code Online (Sandbox Code Playgroud)

暂时忽略JNIEXPORTJNICALL宏.您可能已经注意到,本机方法的C实现接受两个参数,即使本机方法的相应声明不接受任何参数.每个本机方法实现的第一个参数是JNIEnv接口指针.第二个参数是HelloWorld对象本身的引用(有点像thisC++中的" "指针).我们将在本书后面讨论如何使用JNIEnv接口指针和jobject参数,但是这个简单的例子忽略了这两个参数.

2.5编写本机方法实现

生成的JNI样式头文件可javah帮助您为本机方法编写C或C++实现.您编写的函数必须遵循生成的头文件中指定的-prototype.您可以Hello-World.print在C文件中实现该方法,HelloWorld.c如下所示:

#include <jni.h>
#include <stdio.h>
#include "HelloWorld.h"   

JNIEXPORT void JNICALL   Java_HelloWorld_print(JNIEnv *env, jobject obj)  {
     printf("Hello World!\n");
     return;
}
Run Code Online (Sandbox Code Playgroud)

这种原生方法的实现很简单.它使用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源并创建本机库

请记住,当您HelloWorldHelloWorld.java文件中创建类时 ,您包含了一行代码,用于将本机库加载到程序中:

 System.loadLibrary("HelloWorld");   
Run Code Online (Sandbox Code Playgroud)

现在已经编写了所有必需的C代码,您需要编译Hello-World.c和构建此本机库.

不同的操作系统支持不同的方法来构建本机库.在Solaris上,以下命令构建名为libHello-World.so的共享库:

 cc -G -I/java/include -I/java/include/solaris HelloWorld.c -o libHelloWorld.so
Run Code Online (Sandbox Code Playgroud)

-G选项指示C编译器生成共享库而不是常规Solaris可执行文件.由于本书中页面宽度的限制,我们将命令行分为两行.您需要在一行中键入命令,或将命令放在脚本文件中.在Win32,以下命令HelloWorld.dll 使用Microsoft Visual C++编译器构建动态链接库(DLL):

 cl -Ic:\java\include -Ic:\java\include\win32 -MD -LD HelloWorld.c -FeHelloWorld.dll 
Run Code Online (Sandbox Code Playgroud)

-MD选项确保HelloWorld.dllWin32多线程C库链接.该-LD选项指示C编译器生成DLL而不是常规的Win32可执行文件.当然,在Solaris和Win32上,您需要输入反映自己计算机上的设置的包含路径.

2.7运行程序

此时,您已准备好运行该程序的两个组件.类file(HelloWorld.class)调用本机方法,本机库(Hello-World.dll)实现本机方法.

由于HelloWorld该类包含自己的main方法,因此可以在Solaris或Win32上运行该程序,如下所示:

 java HelloWorld
Run Code Online (Sandbox Code Playgroud)

您应该看到以下输出:

   Hello World! 
Run Code Online (Sandbox Code Playgroud)

正确设置本机库路径以使程序运行非常重要.本机库路径是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) 
Run Code Online (Sandbox Code Playgroud)

确保本机库驻留在本机库路径中的一个目录中.如果您在Solaris系统上运行,则LD_LIBRARY_PATH 环境变量用于定义本机库路径.确保它包含包含该libHelloWorld.so文件的目录的名称 .如果libHelloWorld.so文件位于当前目录中,则可以在标准shell(sh)或KornShell(ksh)中发出以下两个命令以LD_LIBRARY_PATH 正确设置环境变量:

 LD_LIBRARY_PATH=.
 export LD_LIBRARY_PATH
Run Code Online (Sandbox Code Playgroud)

C shell(csh或tcsh)中的等效命令如下:

 setenv LD_LIBRARY_PATH .
Run Code Online (Sandbox Code Playgroud)

如果您在Windows 95或Windows NT计算机上运行,​​请确保它HelloWorld.dll位于当前目录中,或者位于PATH环境变量中列出的目录中.

在Java 2 SDK 1.2发行版中,您还可以将java命令行上的本机库路径指定为系统属性,如下所示:

 java -Djava.library.path=. HelloWorld
Run Code Online (Sandbox Code Playgroud)

" -D"命令行选项设置Java平台系统属性.将该java.library.path 属性设置为" ."指示Java虚拟机在当前目录中搜索本机库.

  • 对于 Java &gt;= 9 javah 已被弃用,因此使用 ```javac -h 。HelloWorld.java```。这个问题到处都有答案,但我将其添加到这里只是为了 tl;dr。 (4认同)

San*_*rma 11

简单来说,只需确保加载包含函数定义的相关库,加载遵循JNI规范的库并从第一个库中包装目标函数,从Java类中公开本机方法,你应该好好去.

我建议不要使用原始JNI,因为它包含很多样板代码,如果你开始包装一个大的 C库,你最终会诅咒自己.无论如何,在开始时我们可以随意涉足JNI,但在实际工作中使用像JNA这样的东西.


Daw*_*per 5

您的选择包括:

Java Native Interface
请参阅:https://en.wikipedia.org/wiki/Java_Native_Interface

引用:

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

Java Native Access

请参阅:https://en.wikipedia.org/wiki/Java_Native_Access

引用:

Java Native Access是一个社区开发的库,它为Java程序提供了对本机共享库的轻松访问,而无需使用Java Native Interface.

JNR-FFI

请参阅:https://github.com/jnr/jnr-ffi

引用:

jnr-ffi是一个java库,用于加载本机库,无需手动编写JNI代码,也不使用SWIG等工具.