用JNI实现与原生代码通信

用JNI实现与原生代码通信

1. JNI概述

JNI是Java程序设计语言功能最强的特征,允许Java类的一些方法原生实现,同时让他们能够像普通Java方法一样被调用和使用.

原生方法也可以使用Java对象, 使用方法和Java本身一样.

原生方法可以创建新的Java对象或者使用Java应用程序创建的对象, 这些Java应用程序可以检查,修改和调用这些对象的方法执行相应的任务.

2. JNI示例

1. 原生方法的声明

原生方法使用 native 关键字描述, native 会通知编译器,该方法使用别的语言提供的方法具体实现.

原生方法没有方法体, 方法以 ; 结束.

public native <ReturnType> <NativeMethodName> (<ParamsList>);

public native String jniSayHello (String hello);

2. 加载共享库

原生方法会被编译成一个 共享库.

Java应用需要加载共享库以便虚拟机能够找到原生方法的实现.

java.lang.System 类提供两个静态的方法 load(), loadLibrary(), 用于在运行时加载共享库.

static {
    System.loadLibrary("module");
}
  • module和Android.mk片段中的 LOCAL_MODULE 构建系统变量定义的模块名称相同.

  • loadLibrary的参数不包含共享库的位置,Java库路径即系统属性 java.library.path 保存loadLibrary方法在共享库搜索的目录列表

  • Android系统中的Java库路径有: /vendor/lib 和 /system/lib

  • loadLibrary 在扫描Java库路径的时候, 一旦发现同名的库, 立即加载共享库. 因为Java库路径的第一组目录就是Android系统目录, 为了避免与系统库命名冲突, 强烈建议Android开发者为每一个共享库选择唯一的名称.

3. 实现原生方法

原生方法jniSayHello 使用 Java_com_rygz_jni_HelloJNI_jniSayHello 的完全限定的函数声明,这种显示的函数命名方虚拟机在加载的共享库中自动查找原生函数.

1. C/C++头文件生成器: javah命令

javah 工具可以为原生方法解析Java类文件并生成由原生方法声明组成的头文件.

  1. 用javac命令将.java源文件编译成.class字节码文件
javac src/com/rygz/jni/HelloJNI.java -d ./bin  

//-d 表示将编译后的class文件放到指定的目录下,这里我把它放到和src同级的bin目录下
  1. 用javah -jni命令,根据class字节码文件生成.h头文件(-jni参数是可选的)
javah -classpath bin/classes com.rygz.jni.HelloJNI

javah -jni -classpath ./bin -d ./jni com.rygz.jni.HelloJNI  

//默认生成的.h头文件名为:com_rygz_jni_HelloJNI.h(包名+类名.h),也可以通过-o参数指定生成头文件名称:

javah -jni -classpath ./bin -o HelloJNI.h com_rygz_jni.HelloJNI 

参数说明:

-classpath :类搜索路径,这里表示从当前的bin目录下查找

-d :将生成的头文件放到当前的jni目录下

-o : 指定生成的头文件名称,默认以类全路径名生成(包名+类名.h)

注意:-d和-o只能使用其中一个参数。


  1. 用本地代码实现.h头文件中的函数
#include <jni.h>  
/* Header for class com_rygz_jni_HelloJNI */  

#ifndef _Included_com_rygz_jni_HelloJNII  
#define _Included_com_rygz_jni_HelloJNII  
#ifdef __cplusplus  
extern "C" {  
#endif  
JNIEXPORT jstring JNICALL Java_com_rygz_jni_HelloJNI_jniSayHello(JNIEnv *, jclass, jstring);

#ifdef __cplusplus  
}  
#endif  
#endif  
// HelloJNI.c  

#include "com_rygz_jni_HelloJNI.h"  

#ifdef __cplusplus  
extern "C"{  
#endif
JNIEXPORT jstring JNICALL Java_com_rygz_jni_HelloJNI_jniSayHello( JNIEnv *env, jclass cls, jstring jhello){  
    const char *charString = NULL;  
    char buff[128] = { 0 };  
    charString = (*env)->GetStringUTFChars(env, jhello, NULL);  
    if (charString == NULL){  
        printf("hello is null.\n");  
        return NULL;  
    }  
    printf("Java String:%s\n", charString);  
    sprintf(buff, "hello %s", charString);  
    (*env)->ReleaseStringUTFChars(env, jhello, charString);  
    return (*env)->NewStringUTF(env, buff);  
}  
#ifdef __cplusplus  
}  
#endif  
  1. 将C/C++代码编译成本地动态库文件

动态库文件名命名规则:lib+动态库文件名+后缀(操作系统不一样,后缀名也不一样)如:

Mac OS X : libHelloJNI.jnilib
Windows :HelloJNI.dll(不需要lib前缀)
Linux/Unix:libHelloJNI.so
  1. 在Eclipse IDE中
    (省略)
2. 方法声明

由上知, Java 中jniSayHello是无参数的, 但原生的方法带有两个参数.

JNIEXPORT jstring JNICALL Java_com_rygz_jni_jniSayHello(JNIEnv *, jobject, jstring);

// 第一个参数JNIEnv 是指向可用JNI函数表的接口指针;
// 第二个参数jobject 是HelloJNI类示例的Java对象引用;

函数的命名规则:

用javah工具生成函数原型的头文件,函数命名规则为:Java_类全路径方法名。如Java_com_rygz_jni_HelloJNI_jniSayHello,其中Java是函数的前缀,com_rygz_jni_HelloJNI是类名,jniSayHello是方法名,它们之间用 _(下划线) 连接。

函数参数:

JNIEXPORT jstring JNICALL Java_com_rygz_jni_HelloJNI_jniSayHello(JNIEnv *, jclass, jstring);

第一个参数:JNIEnv*是定义任意native函数的第一个参数(包括调用JNI的RegisterNatives函数注册的函数),指向JVM函数表的指针,函数表中的每一个入口指向一个JNI函数,每个函数用于访问JVM中特定的数据结构。

第二个参数:调用java中native方法的实例或Class对象,如果这个native方法是实例方法,则该参数是jobject,如果是静态方法,则是jclass

第三个参数:Java对应JNI中的数据类型,Java中String类型对应JNI的jstring类型。(后面会详细介绍JAVA与JNI数据类型的映射关系)

函数返回值类型:夹在JNIEXPORT和JNICALL宏中间的jstring,表示函数的返回值类型,对应Java的String类型


  1. JNIEnv 接口指针

原生代码通过JNIEnv 接口指针提供的各种函数来使用虚拟机的功能.

JNIEnv 是一个指向线程-局部数据的指针, 而线程-局部数据中包含指向函数表的指针

实现原生方法的函数将JNIEnv 接口指针作为它们的第一个参数.

  • 传递给每一个原生方法调用的JNIEvn 接口指针在与方法调用相关的线程中也有效, 但它不能被缓存以及被其他线程使用.

  • 原生代码中 C 和C++ 调用JNI 函数的语法不同:

A. C代码中, JNIEnv 是指向JNINativeInterface结构的指针, 为了访问任何一个JNI函数, 该指针需要首先被解引用.因为C代码中JNI函数不了解当前的JNI环境, JNIEnv 实例应该作为第一个参数传递给每一个JNI函数的调用者.

    return (* env)->NewStringUTF(env, "Hello JNI!");

B. C++ 代码中JNIEnv 实际上是C++类的实例, JNI 函数以成员函数的形式存在, 因为JNI方法已经访问了当前的JNI环境, 因此JNI方法调用不要求JNIEnv 实例作为参数.

return env->NewStringUTF("Hello jni!")
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值