android开发教程(十三)——JAVA基础之理解JNI原理(java调用C语言接口)

目录
 
       JNI是JAVA标准平台中的一个重要功能,它弥补了JAVA的与平台无关这一重大优点的不足,在JAVA实现跨平台的同时,也能与其它语言(如C、C++)的动态库进行交互,给其它语言发挥优势的机会。
有了JAVA标准平台的支持,使JNI模式更加易于实现和使用。在此总结了下面这个知识图:


java中调用c函数,主要是通过本地化接口jni来实现的,在windows下,调用的是dll文件,在unix下,调用的是so文件。这里先介绍编写调用c函数的基本步骤:

      (1):编写.java文件,其中c中的函数要用public native static修饰。

      (2):编译.java文件为.class文件,使用javah生成.h文件。

      (3):按照.h文件中的函数形式在c中实现函数。

      (4):生成.dll文件,拷贝到java工程中。

      (5):运行java文件。

      注意以下几点:

      (1)如果java源文件放在包中,一定要在工程目录下使用javah命令。

      (2)在编写c函数时,需要把jdk中的include目录添加到编译标志-I后。

      (3)在java文件中包含如下一句:static{System.loadLibrary("")},引号中为生成的动态连接库文件,不用加扩展名,系统会自动识别的。




实例:
环境说明:
k@k-C410:/data/jdk1.7.0_45$ uname -a
Linux k-C410 3.11.0-14-generic #21-Ubuntu SMP Tue Nov 12 17:04:55 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux

k@k-C410:/data/jdk1.7.0_45$ java -version

java version "1.7.0_25"
OpenJDK Runtime Environment (IcedTea 2.3.12) (7u25-2.3.12-4ubuntu3)
OpenJDK 64-Bit Server VM (build 23.7-b01, mixed mode)

jni头文件目录:
k@k-C410:/data/jdk1.7.0_45/include$ ls
classfile_constants.h  jdwpTransport.h  jvmticmlr.h  linux
jawt.h                 jni.h            jvmti.h

不同平台会有一个对应的子目录,我这里是linux。如果是在windows下则是windows。这个在编译c++文件时需要。

编写java程序:
k@k-C410:/data/test$ vi HelloDll.java

public class HelloDll {

    //用System.loadLibary加载Hello动态库。
    static {
        System.loadLibrary("Hello");
    }
    //用关键字native声明函数为本地函数
    public native static void Display(String Name);
    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub 
        Display("hello world\n");
    }

}
代码说明:
用System.loadLibary加载Hello动态库。
static {
        System.loadLibrary("Hello");
    }

用关键字native声明函数为本地函数:
public native static void Display(String Name);


生成C++接口文件(头文件):
k@k-C410:/data/test$ javah HelloDll
会在当前目录下生成 HelloDll.h 文件。
k@k-C410:/data/test$ ls
HelloDll.h  HelloDll.java
说明:这一步一般是先编译java源文件生成.class文件,现从.class文件生成c++接口文件(头文件)。

在java中,提供了javah这个命令来生成本地方法的头文件。使用命令如下:

javah -classpath A -d B -jni C

其中,A是你的.class文件的路径,这样java就可以搜索到这个.class文件。B是将要生成的头文件的存放目录,可以根据需要指定。C是java类名,在这个例子中就是HelloDll。然后就可以在B目录下看到生成的头文件了。


打开HelloDll.h文件:
k@k-C410:/data/test$ vi HelloDll.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloDll */

#ifndef _Included_HelloDll
#define _Included_HelloDll
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloDll
 * Method:    Display
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_HelloDll_Display
  (JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif

这个就是生成的接口文件。其中jni.h是jdk提供的,它位于jdk根目录中的 include 夹中。
/*
 * Class:     HelloDll
 * Method:    Display
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_HelloDll_Display
  (JNIEnv *, jclass, jstring);

就是要实现的接口。


下面实现接口,编写C++程序:
k@k-C410:/data/test$ vi Hello.cpp

#include <iostream>
#include "HelloDll.h"

/*
 * Class:     HelloDll
 * Method:    Display
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_HelloDll_Display
  (JNIEnv *env, jclass jthis, jstring data)
  {
    jboolean bCopy;
    const char* cData = env->GetStringUTFChars(data, &bCopy);
    std::cout << cData << std::endl;
    env->ReleaseStringUTFChars(data, cData);
  }

现在编译java源文件:
k@k-C410:/data/test$ javac HelloDll.java
会在当前目录下生成中间文件 HelloDll.class
k@k-C410:/data/test$ ls
Hello.cpp  HelloDll.class  HelloDll.h  HelloDll.java

编译C++程序:
k@k-C410:/data/test$ g++ -shared -fPIC -o "libHello.so" -I"/data/jdk1.7.0_45/include/" -I"/data/jdk1.7.0_45/include/linux/"  Hello.cpp
-shared:生成动态库,linux下是libXXX.so ;windows下是 XXX.dll
-fPIC:生成位置无关代码。ubuntu64位系统必需要加
-o:指定生成文件
-I:指定头文件的位置。这里主要是指jdk提供jni头文件。
会在当前目录下生成动态库libHello.so
k@k-C410:/data/test$ ls
Hello.cpp  HelloDll.class  HelloDll.h  HelloDll.java   libHello.so

运行:
k@k-C410:/data/test$ java HelloDll
Exception in thread "main" java.lang.UnsatisfiedLinkError: no Hello in java.library.path
    at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1874)
    at java.lang.Runtime.loadLibrary0(Runtime.java:849)
    at java.lang.System.loadLibrary(System.java:1087)
    at HelloDll.<clinit>(HelloDll.java:5)

运行出错,是因为在java.library.path中找不到动态库Hello.所以我们需要指定java.library.path到当前目录.
k@k-C410:/data/test$ java -Djava.library.path=. HelloDll
hello world

-D<name>=<value>:设置系统属性





为了理解,所以上面是在命令行下分步操作的。但是在真正的项目录中,这会非常麻烦,所以下面介绍在集成开发环境eclipse中的操作步骤:
生成java工程:
File->New->Java Project
android开发教程(十三)——JAVA基础之理解JNI原理(java调用C语言接口)_第1张图片
点Finish。生成工程HelloDll。在工程上点右键->New->Class:
android开发教程(十三)——JAVA基础之理解JNI原理(java调用C语言接口)_第2张图片
点Finish。
在工程中打开HelloDll.java文件。加入以下代码:


//用System.loadLibary加载Hello动态库。
static {
        System.loadLibrary("Hello");
    }
//用关键字native声明函数为本地函数:
public native static void Display(String Name);

生成c++接口文件(头文件)。

在java中,提供了javah这个命令来生成本地方法的头文件。使用命令如下:

javah -classpath A -d B -jni C

其中,A是你的.class文件的路径,这样java就可以搜索到这个.class文件。B是将要生成的头文件的存放目录,可以根据需要指定。C是java类名,在这个例子中就是HelloDll。然后就可以在B目录下看到生成的头文件了。

首先在eclipse中点击下图图标的下拉菜单

android开发教程(十三)——JAVA基础之理解JNI原理(java调用C语言接口)_第3张图片

然后点击External Tools Configurations

会弹出下面的页面,然后在那3个地方分别填入下图中的命令

其实,这里的操作就是把javah这个可执行程序当做外部工具引入eclipse中来了。调用的时候,eclipse就会执行javah,并且使用预设的arguments,即:

javah -classpath .;./classes -d "${project_loc}“ -jni ${java_type_name}

可以看到,.class文件的搜索目录是当前工作目录和子目录classes(android开发时生成的.class是在classes这个子目录中,所以这里也加了这个目录,如果你不搞android开发,那不加这个目录也没关系)。当前工作目录就是上面设好的${project_loc}/bin,也就是你这个java工程的bin文件夹。这样javah就可以在该目录下找到本地方法的.class文件。-d "${project_loc}"是为了让生成好的.h头文件直接被放置在java工程目录下,便于后续操作。 最后的${java_type_name}是动态的参数,你在生成.h头文件之前先在eclipse中选中本地方法那个java文件,这样java_type_name就自动变成了这个本地方法的名字。

设置好以后,我们来试着生成一下头文件,先在eclipse中选中刚刚定义好的本地方法Example.java,然后点击下图中的位置来调用javah这个外部工具,就可以自动生成头文件了(需要在当前工程中刷新一下才能看到)。

android开发教程(十三)——JAVA基础之理解JNI原理(java调用C语言接口)_第4张图片

这样设置好以后,将来各位同学在需要生成头文件时,只需写好本地方法的java类,然后鼠标点击一下就能够自动生成头文件了。

生成C++接口实现动态库:

File->New->Project->c/c++->c++ project

在Hello工程点右键 New->Source File

android开发教程(十三)——JAVA基础之理解JNI原理(java调用C语言接口)_第5张图片

在Hello.cpp文件中加入下列代码:

#include <iostream>
#include "HelloDll.h"

/*
 * Class:     HelloDll
 * Method:    Display
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_HelloDll_Display
  (JNIEnv *env, jclass jthis, jstring data)
  {
    jboolean bCopy;
    const char* cData = env->GetStringUTFChars(data, &bCopy);
    std::cout << cData << std::endl;
    env->ReleaseStringUTFChars(data, cData);
  }

设置包含头文件位置:

jni头文件位置

在Hello工程上右键->属性:

android开发教程(十三)——JAVA基础之理解JNI原理(java调用C语言接口)_第6张图片

设置工程为动态库:

android开发教程(十三)——JAVA基础之理解JNI原理(java调用C语言接口)_第7张图片

设置-fPIC标志:

android开发教程(十三)——JAVA基础之理解JNI原理(java调用C语言接口)_第8张图片

编译,可以生成动态库libHello.so


设置把libHello.so自动复制到HelloDll中的bin目录下:

android开发教程(十三)——JAVA基础之理解JNI原理(java调用C语言接口)_第9张图片

 
   
 
  

你可能感兴趣的:(android开发教程(十三)——JAVA基础之理解JNI原理(java调用C语言接口))