JNI 原理与入门

来源:互联网 发布:魔术师约翰逊生涯数据 编辑:程序博客网 时间:2024/06/06 13:22

  • JNI 是什么
  • HelloWorld
    • 编写 java 文件
    • 编译 java 文件
    • 编写 c 语言文件
    • 编译 c 语言文件
    • 运行
  • Android 中的 JNI 简介
    • NDK
    • Native 层和 JNI 层
  • 附录
    • 附录1让 Java 自动帮我们生成C函数名
    • 附录2如何生成 dll
      • 方法1 使用 VisualStudio
      • 方法2 使用 cl 命令行


JNI 是什么

  • JNI = Java Native Interface
  • 是java调用C语言用的
  • 是java调用 “用C语言代码编译出来的库” 用的
  • 因为用C语言编写的程序是不可移植的,是平台相关的,所以这种代码一般叫做Native Code
  • 举例来说,用C语言在Windows上写了一个HelloWorld,用VC编译成了dll,这个dll拿到Linux上是运行不起来的。
  • 这就是为什么叫做 “Native” Interface了

HelloWorld

  • 我们先搞个 JNI 的 helloworld,来个感性的认识。
  • 本例实现的是一个 java 的 helloworld 程序,但其中的打印 “HelloWorld!” 的功能,是用 C 语言实现的。
  • 本例基于 Windows7、JDK1.6,用记事本和cmd命令行完成,下面是详细步骤。

1. 编写 java 文件

  • 用记事本写一个最简单的 HelloWorld.java
  • 其中打印“HelloWorld”那句话交给C语言去完成,而不再 System.out.println()
public class HelloWorld {    public native void displayHelloWorld();    static {        System.loadLibrary("hello");    }    public static void main(String[] args) {        new HelloWorld().displayHelloWorld();    }}
  • 1:public native void displayHelloWorld(); 这句话把 displayHelloWorld() 方法声明为 native 的,也就是说这个方法由C语言的库来提供,我们不要去实现它。那么C语言的库在哪里呢?
  • 2:System.loadLibrary("hello"); 这句话的意思就是加载C语言的 hello 这个库,这个库在Windows上是一个叫 hello.dll 的文件,一会儿我们会写相应的C代码去实现 displayHelloWorld(),并编译成dll文件供java使用。
  • 3:System.loadLibrary("hello"); 这句话一般写在 static 块里,static{}不是这篇的重点,不解释

2. 编译 java 文件

  • 我们先把这个 HelloWorld.java 编译成class字节码:
javac HelloWorld.java

3. 编写 c 语言文件

  • 下面我们用C语言来实现 displayHelloWorld()
  • 记事本新建一个文件叫 HelloWorldC.c,内容如下:
#include "jni.h"JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld(JNIEnv *env, jobject obj){    printf("Helloworld!\n");    return;}
  • 1:include 必须要有,jni.h 是 java sdk 提供给我们的,具体路径在:%java_home%\include
  • 2:这个函数名必须是前缀 Java_包名_类名_C函数名,参数必须是 (JNIEnv* env,jobject obj,其他参数,..)
  • 3:函数名和参数太长记不住没关系,我们后面会讲如何借助工具自动生成。

4. 编译 c 语言文件

  • 写好c文件之后,我们编译它,生成 dll。
  • 如何把c文件编译生成dll不是本文的重点,我们必须得到 hello.dll 就对了。关于如何编译,本文附录部分会提供两种方法。
  • 注意,生成的dll文件名必须叫 hello.dll,因为我们的java代码里 load 的就是 “hello”(System.loadLibrary("hello");

5. 运行

  • 好了,保证 hello.dll 和 HelloWorld.class 在同一目录下,我们运行:
java HelloWorld
  • 没问题的话,就可以看到打印出 HelloWorld 了
  • 这个例子说明 java 确实可以使用 c 实现的功能。

Android 中的 JNI 简介

  • Android 系统的底层是 Linux,Linux 是由大量的 c 语言库构建起来的
  • 而 Android 的应用程序却都是 Java 开发的。所以 Android 系统中大量的使用了 JNI。
  • 我们上面的 HelloWorld, 是运行在 Windows 的 java 程序去调用 Windows 上的 c 库函数。
  • 而在 Android 上,一般是 App(即运行在 Android 上的 java 程序) 去调用 Android 上的 c 库函数。
  • 也就是说,如果我们上面的 HelloWorld 如果想在 Android 上做,则:
    1. HelloWorld.java 要变成 HelloWrold.apk。HelloWrold.apk 起码要有一个 Activity,上面起码要有个 TextView,来显示 “HelloWorld!”
    2. hello.dll 要变成 hello.so。不再是用 VC 生成 dll,而是要用 gcc 或者什么东西,生成一个可以在 Android 上运行的 so 库。
  • 其中第一个问题好办,我们用 eclipse 或 AndroidStudio 可以轻松搞一个 apk。
  • 第二个问题需要把 c 语言代码编译成能在 Android 上运行的库文件,这需要 Google 的专门工具:NDK。

NDK

  • NDK 是 Native Development Kit 的缩写,是谷歌提供的 C/C++ 编译工具链,专为 Android 准备。
  • NDK 本质上是编译器、连接器的集合。编译器是 gcc/clang,连接器是 ld,等等。
  • 简单来说,NDK 就是一个 toolchain,能把 c/c++ 源码编译成运行在 Android 上的库文件/可执行文件。
  • 他的使用方式跟 gcc 很像:书写 Makefile 来描述你项目的组织结构,使用 make 命令来调用 gcc 工具链,实现编译目标。
  • NDK的安装:只需要下载解压即可,解压之后可以拷贝到任意目录。下载地址:http://developer.android.com/tools/sdk/ndk/index.html
  • 使用 NDK 的目的就是把 c/c++ 的源文件编译成库,供 App 调用。
  • 使用 NDK 大体上有两种方式:
    • 集成到IDE里,比如 eclipse 或者 AndroidStudio
    • 直接使用命令行
  • 无论哪种方式,其本质都是调用 NDK 的命令行:ndk-build,让 ndk 根据你书写的 Android.mk 去把你的 c 代码编译成库。
  • 使用 NDK 的关键是编写 makefile。即通过 makefile 告诉 NDK,该把哪些源文件编译成哪个库。
  • NDK 使用的 makefile 是经过谷歌改良的,跟正常的 makefile 语法稍有不同。一般叫做 Android.mk。
  • 具体用法本文不展开。

Native 层和 JNI 层

  • 我们现在知道了 Java 层和 JNI 层,一个是 Java 写的,一个是 C/C++ 写的。
  • 其实一般用 C/C++ 写的程序,还分为两层:Native 层和 JIN 层。
  • 为什么有这个区分呢?
  • 所谓 Native 层,是不需要知道有 Java 层存在的,它只负责干活就行了。比如一些已有的 c 语言库,它的开发者根本不知道将来需要被 Java 层使用的。
  • 而 JNI 层是知道 Java 层存在的,它往往不真的干活,而只是把 Java 的调用转交给真正干活的 Native 层去处理。

附录

附录1:让 Java 自动帮我们生成C函数名

  • Java提供了一个工具:javah.exe,能根据你的 Java 代码,自动帮你生成需要的C函数的函数名,并保存成一个.h文件。
  • 具体用法:
javah 包名.类名
  • 注意:jdk 1.6 javah 需要的参数是.class文件,jdk 1.7 需要的是.java文件。
  • 也就是说,如果你用的是 jdk1.6,则需要先把 java 编译成字节码才能运行 javah。
  • 在我们上面的例子里(jdk1.6),编译好 HelloWorld.class后,我们运行:
javah HelloWorld
  • 注意:保证执行 javah 命令的目录中存在 HelloWorld.class。注意不要写成 javah HelloWorld.class

  • 执行完 javah 命令之后,会生成一个 HelloWorld.h 文件,其内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class HelloWorld */#ifndef _Included_HelloWorld#define _Included_HelloWorld#ifdef __cplusplusextern "C" {#endif/* * Class:     HelloWorld * Method:    displayHelloWorld * Signature: ()V */JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld  (JNIEnv *, jobject);#ifdef __cplusplus}#endif#endif
  • 可以看到,他自动生成了函数名及参数,我们可以直接从里面复制。当工程量大了以后,生成这个头文件给C语言程序员去用是必要的。

附录2:如何生成 dll

方法1. 使用 VisualStudio

  • 使用的是 VS2010,其他版本应该大同小异
  • 新建项目时,要选择DLL项目而不是控制台应用程序项目。对VS2010来说,具体就是:
    选“Win32项目”,而不是“Win32控制台应用程序”。Win32项目点确定 –> 下一步可以选DLL。

  • 把 jin.h 和 jni_md.h 让项目能引用到。

    • 要么就去 jdk 的 include 下把他俩复制到你项目里,
    • 要么就在项目上右键–>属性,C/C++—>常规—>附加包含目录,填写 <your_jdk_path>/include<your_jdk_path>/include/win32
  • VS默认生成的都是cpp文件,按c++编译。这样编译出来的 DLL 不能直接被 java 使用。有以下解决方法:

    • 要么所有源文件后缀名改成.c,重新生成项目。
    • 要么在头文件里加上如下内容(就是从 javah 生成的那个头文件里复制出来的):

      #ifdef __cplusplus extern "C" {#endifJNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld  (JNIEnv *, jobject);#ifdef __cplusplus }#endif
    • 或者你直接 include javah 生成的那个头文件,就不用去解决cpp不兼容的问题了。

方法2. 使用 cl 命令行

  • cl.exe 是 vc6.0 的编译器
  • 网上可以下载到绿色版,叫 mycl.rar,挺小的,才1.53M
  • 解压后直接使用其中的 cl.exe 即可
  • 使用之前需要先设置如下环境变量:
CL_HOME=D:\ProgramFiles\mycl把 CL_HOME 加到 path 里去INCLUDE=%CL_HOME%\includeLIB=%CL_HOME%\lib
  • cl 的帮助可以用 cl /? 调出来

  • 编译以上 c 语言文件的完整命令行如下:

cl -I "%java_home%\include"  -I "%java_home%\include\win32" -LD  HelloWorldC.c -Fehello.dll
  • 1:其中 -I 表示 include 的搜索路径 ,我们必须把”%java_home%\include”和%java_home%\include\win32”加上
  • 2:-LD 表示需要生成dll
  • 3:-Fe表示输出文件,后面紧跟文件名

  • 另外,如果编译时提示LIBCMT.LIB找不到,可下载个VC6.0的安装包,在…./VC98/LIB/ 下可以找打这个文件,把它复制到mycl的LIB目录下即可了。

0 0
原创粉丝点击