【Android -- JNI 和 NDK】JNI 基础知识以及如何使用

在这里插入图片描述

JNI 基础知识

我们来系统梳理一下JNI中涉及的基本知识。
JNI定义了以下数据类型,这些类型和Java中的数据类型是一致的:

  1. Java原始类型:jint, jbyte, jshort, jlong, jfloat, jdouble, jchar, jboolean这些分别对应这 java 的int, byte, short, long, float, double, char and boolean
  2. Java引用类型:jobject 用来指代 java.lang.Object,除此之外,还定义了以下子类型:
    a. jclass for java.lang.Class.
    b. jstring for java.lang.String.
    c. jthrowable for java.lang.Throwable.
    d. jarray对java的array。java的array是一个指向8个基本类型array的引用类型。于是,JNI中就有8个基本类型的array:jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray 和 jbooleanArray,还有一个就是指向Object的jobjectarray。
    Native函数会接受上面类型的参数,并且也会返回上面类型的返回值。然而,本地函数(C/C++)是需要按照它们自己的方式处理类型的(比如C中的string,就是char *)。因此,需要在JNI类型和本地类型之间进行转换。通常来讲,本地函数需要:
  3. 加收JNI类型的参数(从java代码中传来)
  4. 对于JNI类型参数,需要讲这些数据转换或者拷贝成本地数据类型,比如讲jstring转成char *, jintArray转成C的int[]。需要注意的是,原始的JNI类型,诸如jint,jdouble之类的不用进行转换,可以直接使用,参与计算。
  5. 进行数据操作,以本地的方式
  6. 创建一个JNI的返回类型,然后讲结果数据拷贝到这个JNI数据中
  7. returnJNI类型数据

这其中最麻烦的事莫过于在JNI类型(如jstring, jobject, jintArray, jobjectArray)和本地类型(如C-string, int[])之间进行转换这件事情了。不过所幸的是,JNI环境已经为我们定义了很多的接口函数来做这种烦人的转换。

使用

使用 C 来实现 JNI
步骤1,编写一个使用C实现函数的java类,HelloJNI.java

public class HelloJNI {
    
    
   static {
    
    
      System.loadLibrary("hello"); // Load native library at runtime
                                   // hello.dll (Windows) or libhello.so (Unixes)
   }

   // Declare a native method sayHello() that receives nothing and returns void
   private native void sayHello();

   // Test Driver
   public static void main(String[] args) {
    
    
      new HelloJNI().sayHello();  // invoke the native method
   }
}

上面代码的静态代码块在这个类被类加载器加载的时候调用了System.loadLibrary()方法来加载一个native库“hello”(这个库中实现了sayHello函数)。这个库在windows品台上对应了“hello.dll”,而在类UNIX平台上对应了“libhello.so”。这个库应该包含在Java的库路径(使用java.library.path系统变量表示)上,否则这个上面的程序会抛出UnsatisfiedLinkError错误。你应该使用VM的参数-Djava.library.path=path_to_lib来指定包含native库的路径。

接下来,我们使用native关键字将sayHello()方法声明为本地实例方法,这就很明显地告诉JVM:这个方法实现在另外一个语言中(C/C++),请去那里寻找他的实现。注意,一个native方法不包含方法体,只有声明。上面代码中的main方法实例化了一个HelloJJNI类的实例,然后调用了本地方法sayHello()。
下面,我们编译HelloJNI.java成HelloJNI.class

javac HelloJNI.java

接下来,我们利用上面生成的class文件生成用于编写C/C++代码的头文件,使用jdk中的javah工具完成:

javah HelloJNI

上面的命令执行完之后生成了HelloJNI.h:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloJNI */

#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
    
    
#endif
/*
 * Class:     HelloJNI
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

我们看到,上面的头文件中生成了一个Java_HelloJNI_sayHello的C函数:

JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);

将java的native方法转换成C函数声明的规则是这样的:Java_{package_and_classname}_{function_name}(JNI arguments)。包名中的点换成单下划线。需要说明的是生成函数中的两个参数:

  1. JNIEnv *:这是一个指向JNI运行环境的指针,后面我们会看到,我们通过这个指针访问JNI函数
  2. jobject:这里指代java中的this对象

下面我们给出的例子中没有使用上面的两个参数,不过后面我们的例子会使用的。到目前为止,你可以先忽略JNIEXPORT和JNICALL这两个玩意。
上面头文件中有一个extern “C”,同时上面还有C++的条件编译语句,这么一来大家就明白了,这里的函数声明是要告诉C++编译器:这个函数是C函数,请使用C函数的签名协议规则去编译!因为我们知道C++的函数签名协议规则和C的是不一样的,因为C++支持重写和重载等面向对象的函数语法。
接下来,我们给出C语言的实现,以实现上面的函数:

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

// Implementation of native method sayHello() of HelloJNI class
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
    
    
   printf("Hello World!\n");
   return;
}

将上面的代码保存为HelloJNI.c。jni.h头文件在 “\include” 和 “\include\win32”目录下,这里的JAVA_HOME是指你的JDK安装目录。
这段C代码的作用很简单,就是在终端上打印Hello Word!这句话。
下面我们编译这段代码,使用GCC编译器:
对于windows上的MinGW:

> set JAVA_HOME=C:\Program Files\Java\jdk1.7.0_{xx}
      // Define and Set environment variable JAVA_HOME to JDK installed directory
      // I recommend that you set JAVA_HOME permanently, via "Control Panel" ⇒ "System" ⇒ "Environment Variables"
> echo %JAVA_HOME%
      // In Windows, you can refer a environment variable by adding % prefix and suffix 
> gcc -Wl,--add-stdcall-alias -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -shared -o hello.dll HelloJNI.c
      // Compile HellJNI.c into shared library hello.dll

也可以分步编译:

// Compile-only with -c flag. Output is HElloJNI.o
> gcc -c -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" HelloJNI.c

// Link into shared library "hello.dll"
> gcc -Wl,--add-stdcall-alias -shared -o hello.dll HelloJNI.o

下面,我们使用nm命令来查看生成hello.dll中的函数:

> nm hello.dll | grep say
624011d8 T _Java_HelloJNI_sayHello@8

对于windows上的Cygwin:
首先,你需要讲__int64定义成“long long”类型,通过-D _int64=”long long选项实现。
对于gcc-3,请包含选项-nmo -cygwin来编译dll库,这些库是不依赖于Cygwin dll的。

> gcc-3 -D __int64="long long" -mno-cygwin -Wl,--add-stdcall-alias 
  -I"<JAVA_HOME>\include" -I"<JAVA_HOME>\include\win32" -shared -o hello.dll HelloJNI.c

使用C/C++混合实现JNI
第一步:编写一个使用本地代码的java类:HelloJNICpp.java

public class HelloJNICpp {
    
    
   static {
    
    
      System.loadLibrary("hello"); // hello.dll (Windows) or libhello.so (Unixes)
   }

   // Native method declaration
   private native void sayHello();

   // Test Driver
   public static void main(String[] args) {
    
    
      new HelloJNICpp().sayHello();  // Invoke native method
   }
}

同样地,我们使用javac来编译这个代码:

> javac HelloJNICpp.java

步骤2:生成C/C++的头文件

> javah HelloJNICpp

上面命令会生成一个HelloJNICpp.h的文件,并且这个文件中声明了这个本地函数:

JNIEXPORT void JNICALL Java_HelloJNICpp_sayHello(JNIEnv *, jobject);

步骤3:C/C++编码实现,HelloJNICppImpl.h, HelloJNICppImpl.cpp, 和 HelloJNICpp.c
这里,我们使用C++来实现真正的函数(”HelloJNICppImpl.h” 和 “HelloJNICppImpl.cpp”),而使用C来和java进行交互。(译者注:这样就可以把JNI的代码逻辑和我们真正的业务逻辑分离开了!)
C++头文件:”HelloJNICppImpl.h”

#ifndef _HELLO_JNI_CPP_IMPL_H
#define _HELLO_JNI_CPP_IMPL_H

#ifdef __cplusplus
        extern "C" {
    
    
#endif
        void sayHello ();
#ifdef __cplusplus
        }
#endif

#endif

C++的代码实现:”HelloJNICppImpl.cpp”

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

using namespace std;

void sayHello () {
    
    
    cout << "Hello World from C++!" << endl;
    return;
}

C代码实现和Java的交互:”HelloJNICpp.c”

#include <jni.h>
#include "HelloJNICpp.h"
#include "HelloJNICppImpl.h"

JNIEXPORT void JNICALL Java_HelloJNICpp_sayHello (JNIEnv *env, jobject thisObj) {
    
    
    sayHello();  // invoke C++ function
    return;
}

讲上面的代码编译成一个共享库(在windows上是hello.dll)。
使用windows上的MinGW GCC:

> set JAVA_HOME=C:\Program Files\Java\jdk1.7.0_{xx}
> g++ -Wl,--add-stdcall-alias -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" 
      -shared -o hello.dll HelloJNICpp.c HelloJNICppImpl.cpp

步骤4:运行java代码

> java HelloJNICpp
or
> java -Djava.library.path=. HelloJNICpp

猜你喜欢

转载自blog.csdn.net/duoduo_11011/article/details/131196132