JNI 原理与入门


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 __cplusplus
extern "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" {
      
      #endif
      
      JNIEXPORT 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%\include
LIB=%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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值