简单JNI的使用在Java中调用C库函数

  在Android Framework中,需要提供一种媒介或桥梁,将Java层(上层)与C/C++(底层)有机地联系起来,使得它们相互协调,共同完成某些任务。在这两层之间充当连接桥梁这一角色的就是Java本地接口(JNI,Java Native Interface),它允许Java代码与基于C/C++编写的应用和库进行交互操作。

  JNI提供了一系列接口,允许Java类与使用C/C++等其它编程语言(在JNI中,这些语言被称为本地语言)编写的应用程序、模块、库进行交互操作。比如,在Java类中使用C语言库中中的特定函数,或在C语言里面使用Java类库,都需要借助JNI来完成。

  通常会在下列几种情况下使用JNI

  • 注重处理速度:如果对某段程序的执行速度有较高的要求,建议使用C/C++编写代码,而后在Java层通过JNI调用基于C/C++编写的部分代码。
  • 硬件控制:为了更好地控制硬件,硬件控制代码通常使用C语言编写,借助JNI将其与Java层连接起来,从而实现对硬件的控制。
  • 已有C/C++代码的复用:在编写程序的过程中,常常会使用已经编写好的C/C++代码,既提高了编程效率,又确保了程序的安全性和健壮性。在复用这些C/C++代码时,就要通过JNI来实现。

在Java代码中通过JNI调用C函数的步骤如下:

  • 第一步:编写Java代码
  • 第二步:编译Java代码(javac Java文件)
  • 第三步:生成C代码头文件(javah java类名,自动生成)
  • 第四步:编写C代码(实现C代码头文件里面的函数)
  • 第五步:生成C共享库(使用工具编译生成C共享库,win下面为dll文件,Linux下面为so文件)
  • 第六步:运行Java程序(java 类名)

第一步:编写Java代码

       首先编写调用C语言的Java源代码HelloJNI.java     

 public class HelloJNI {
          native void printHello();                      1
          native void printString(String str);
         static{
            System.loadLibrary("hellojni");        2
        }
          public static void main(String[] args) {
             // TODO Auto-generated method stub
             HelloJNI myJNI = new HelloJNI();
               myJNI.printHello();
             myJNI.printString("Hello world form printString function!");
          }
}

说明:

     1在Java类中,使用”native”关键字,声明本地方法,该方法与用C/C++编写的JNI本地函数相对应。”native”关键字告知Java编译器,在Java代码中带有该关键字的方法只是声明,具体由C/C++等其它语言编写实现。

       2在Java类中声明了本地方法之后,接下来,调用System.loadLibrary()方法,加载具体实现本地方法的C运行库(在Java中加载本地运行库通常使用静态块(static block))。System.loadLibrary()方法加载由字符串参数指定的本地库,在不同操作系统平台下,加载的C运行库不同。在Window下面,调用System.loadLibrary(“hellojni”),则hellojni.dll会被加载;在Linux下面,则会加载libhellojni.so文件。

第二步:编译Java代码

       使用如下命令编译java源代码:

              javac HelloJNI.java

       编译好HelloJNI.java后,生成HelloJNI.class文件。如果此时直接运行java程序,就是抛出异常。

 

       由于尚未创建加载到Java代码中的hellojni.dll库文件,无法找到Java虚拟机要加载的C运行库。

       接下来,创建hellojni.dll库文件。

第三步:生成C代码头文件

       若想创建本地方法的映射C函数,必须先生成函数原型,函数原型存在于C/C++头文件中。Java提供了javah工具,位于JAVA JDK的安装目录的bin目录下面,用来生成包含函数原型的C/C++头文件,使用方法如下:

       javah <包含以native关键字声明方法的Java类名称>

       运行javah命令,会在当前目录下生成与Java类名(即javah命令的参数)相同名称的C语言头文件。在生成的C头文件中,定义了与Java本地方法相链接的C函数原型。

       以下为生成的HelloJNI.h文件内容:

/* DO NOT EDIT THIS FILE - it is machine generated */          1

#include <jni.h>

/* Header for class HelloJNI */

 

#ifndef _Included_HelloJNI

#define _Included_HelloJNI

#ifdef __cplusplus

extern "C" {

#endif

/*

 * Class:     HelloJNI

 * Method:    printHello

 * Signature: ()V

 */

JNIEXPORT void JNICALL Java_HelloJNI_printHello  

  (JNIEnv *, jobject);

 

/*

 * Class:     HelloJNI                                                                          2

 * Method:    printString

 * Signature: (Ljava/lang/String;)V

 */

JNIEXPORT void JNICALL Java_HelloJNI_printString

  (JNIEnv *, jobject, jstring);

 

#ifdef __cplusplus

}

#endif

#endif

说明:

       1该文件由javah命令生成,为了保证JNI正常运行,请不要直接修改本文件的内容,JNI开发者只要使用C/C++语言实现定义的函数即可。

       2这是javah命令生成的两个C函数原型,函数原型在Java类中声明的本地方法的基础上生成。查看各函数原型注释,可以看到与各函数原型对应的Java代码中的本地方法,注释中标明了三个元素:类名、本地方法、本地方法签名。     

       接下来分析一下函数原型:JNIEXPORT、JNICALL都是JNI关键字,表示此函数要被JNI调用,函数原型中必须有这两个关键字,JNI才能正常调用函数。其实,JNIEXPORT、JNICALL两个关键字都是宏定义,在JDK_HOME/include/win32/jni_md.h文件(在Window平台下)中。

       观察函数原型名称,可以发现函数名称遵循一定的命名规则:JNI支持的函数命名形式为”Java_类名_本地方法名”。通过函数命名即可推断出JNI本地函数与哪个Java类的哪个本地方法相对应。

       在生成的函数原型中,带有两个默认参数,分别为JNIEnv * 与jobject,支持JNI的函数必须包含这两个共同参数。第一个参数JNIEnv *为JNI接口指针,用来调用JNI表中的各种JNI函数(这里的JNI函数是指JNI中提供的基本函数集);第二个参数jobject也是JNI提供的Java本地方法,用来在C代码中访问Java对象,此参数中保存着调用本地方法的对象的一个引用。

       在JNI编程中,Java程序与C/C++函数间经常进行数据交换,如果不提供一种方法消除两种语言的数据类型的差异,那么程序就无法正常运行,运行的可靠性也无法保障。JNI提供了一套与Java数据类型相对应的Java本地类型,使得本地语言可以使用Java数据类型,如下表所示:

Java类型

本地类型

字节(bit)

boolean

jboolean

8,

byte

jbyte

8

char

jchar

16,

short

jshort

16

int

jint

32

long

jlong

64

float

jfloat

32

double

jdouble

64

void

void

n/a

       以上Java本地类型定义在JDK_HOME/include/jni.h文件中。此外,Java本地类型也提供了另外三种类型,分别对应于Java类、对象与字符串三种引用类型数据(除此之外还有几种引用类型,有兴趣可以可以查阅相关资料)。

Java引用类型

Java本地类型

Jclass

对象

Jobject

String

Jstring

 

第四步:编写C代码

       在C函数原型生成后,开始编写hellojni.c文件,具体实现JNI本地函数。首先,把定义在HelloJNI.h头文件中的函数原型复制到hellojni.c,注意在使用javah命名生成的头文件中,函数的参数仅指定了参数的类型,并未给出参数的名称,因此复制完函数原型,开始实现C函数时,必须先在参数类型后指定具体的参数名称。

       以下为编写好的hellojni.c代码:

#include "HelloJNI.h"

#include <stdio.h>

 

//添加名称为env与obj的两个参数

JNIEXPORT void JNICALL Java_HelloJNI_printHello(JNIEnv *env, jobject obj)

{

       printf("Hello world!\n");

       return ;

}

 

JNIEXPORT void JNICALL Java_HelloJNI_printString(JNIEnv *env, jobject obj, jstring string)

{

       //将Java String转换以C字符串

       const char * str = (*env)->GetStringUTFChars(env,string,0);

       printf("%s!\n",str);

       return ;

}

       GetStringUTFChars ()是JNI函数,用来将Java字符串转换成C语言字符串。JNI提供了多种JNI函数,用来处理C字符串与Java字符串的转换,具体可以参考:

第五步:生成C共享库

       在编写好了hellojni.c之后,使用编译器将其编译成hellojni.dll文件。这里使用的是Visual C++ 2008 Express Editions。安装好之后,使用Visual Studio 2008 命令提示,输入编译指令:

cl -I"F:\Java\jdk1.7.0\include" -I"F:\Java\jdk1.7.0\include\win32" -LD hellojni.c -Fehellojni.dll

执行结果为:

 

指令说明:

       cl:visual c++编译命令

       -I<dir>:添加要检索头文件的目录路径<dir>

                 为了检索头文件,添加如下目录

                     jni.h(<JDK_HONE>\include)

                     jni_md.h(JDK_HONE>\include\win32)

       -LD:创建DLL

       -Fe<文件名>:指定编译结果文件名称

第六步:运行Java程序

       执行java指令,运行HelloJNI类。

      

附录:

  生成JNI的DLL时提示找不到jni.h的解决办法Cannot open include file: 'jni.h': No such file or directory

  解决方法:在Windows下面使用cygwin将含有JNI的C文件编译成DLL文件

原文地址:https://www.cnblogs.com/adm1989/p/2849514.html