2015-01-28 21:55:58 frisky1985 阅读数 429
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57548 人正在学习 去看看 李宁

Java JNI是一种静态注册native函数的方式。它的缺点是对每一个声明有native函数的java都需要生成.h,然后再做成链接库。相对繁琐。

其实java native函数和JNI函数的一一对应关系可以理解为函数和函数指针的关系,因此可以存在一种动态注册的方式注册函数。

Andoird 中使用了一种JNINativeMethod结构体还存放这种关联关系,即Andorid使用了一种Java 和 C 函数的映射表数组,并在其中描述了函数的参数和返回值。这个数组的类型是JNINativeMethod,定义如下:

typedef struct {
const char* name;         // native函数名称
const char* signature;  // native函数签名,主要包括函数参数类型和返回值
void* fnPtr;                       //函数指针,用于创建关联关系
} JNINativeMethod;

第一个变量name是Java中函数的名字。

第二个变量signature,用字符串是描述了函数的参数和返回值

第三个变量fnPtr是函数指针,指向C函数。

其中比较难以理解的是第二个参数,例如

"()V"

"(II)V"

"(Ljava/lang/String;Ljava/lang/String;)V"

实际上这些字符是与函数的参数类型一一对应的。

"()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Func();

"(II)V" 表示 void Func(int, int);

具体的每一个字符的对应关系如下

字符 Java类型 C类型

V      void            void
Z       jboolean     boolean
I        jint              int
J       jlong            long
D      jdouble       double
F      jfloat            float
B      jbyte            byte
C      jchar           char
S      jshort          short

数组则以"["开始,用两个字符表示

[I       jintArray      int[]
[F     jfloatArray    float[]
[B     jbyteArray    byte[]
[C    jcharArray    char[]
[S    jshortArray   short[]
[D    jdoubleArray double[]
[J     jlongArray     long[]
[Z    jbooleanArray boolean[]

上面的都是基本类型。如果Java函数的参数是class,则以"L"开头,以";"结尾中间是用"/" 隔开的包及类名。而其对应的C函数名的参数则为jobject. 一个例外是String类,其对应的类为jstring

Ljava/lang/String; String jstring
Ljava/net/Socket; Socket jobject

如果JAVA函数位于一个嵌入类,则用$作为类名间的分隔符。

例如 "(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z"

2019-03-20 15:52:27 lyz_zyx 阅读数 186
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57548 人正在学习 去看看 李宁

1 前言

前面两篇文章中,已经对JNI有了一些介绍。现在我们来回顾一下,它主要是通过使用javac -h命令来生成了一个.h的头文件,来产生Java和Native两边方法函的注册关联。这样当Java代码中去执行Native方法的时候,就会通过两边的关联的映射关系来找到这些Native真正实现的地方。事实上,JNI有两种关联Native方法的途径,分别是静态注册和动态注册。

2 注册方式

2.1 静态注册

前面文章所列举的Demo中使用命令生成.h头文件的方式就是使用了静态注册的方式。可以发现通过这种方式生成的.h头文件中,函数的名称是有规律的,如:Java_com_zyx_jnidemo_JNIUtils_getInfo,规律就是:Java前缀 + Native方法所在类的全称(用_替换.) + Native方法名。

静态注册的好处就是全自动生成操作方便,但缺点也是相当明显,就是函名过长和不够灵活。

2.2 动态注册

动态注册是通过RegisterNatives函数来将Java层和Native层的方法和函数动态关联起来,而且无需遵循特定的方法命名格式,使得代码可以更加的灵活。

当我们在Java代码中使用System.loadLibarary()方法来加载so文件时,Java虚拟机就会去调用JNI_OnLoad函数,该函数的作用是告诉虚机机应该使用哪个版本的JNI,如果我们没有指定,它默认是使用JNI1.1版本。JNI_OnLoad函数中还可以做一些初始化的事件,它对应反加载函数是JNI_OnUnload。所以我们要想进行Native函数的动态注册,最佳的时机就是在JNI_OnLoad函数中去执行。

4 继续修改Hello World

继续使用前面文章中的Demo,这次我们不通过命令生成.h头文件,而且自己创建一个新的.h头文件JNIUtils.h,内容如下:

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

#ifndef _Included_com_zyx_jnidemo_JNIUtils
#define _Included_com_zyx_jnidemo_JNIUtils
#ifdef __cplusplus
extern "C" {
#endif

/*
 * Class:     com_zyx_jnidemo_JNIUtils
 * Method:    getInfo
 * Signature: ()Ljava/lang/String;
 */
//JNIEXPORT jstring JNICALL Java_com_zyx_jnidemo_JNIUtils_getInfo
//  (JNIEnv *, jclass);


/*
 * Native层对应Java层中的方法
 */
static jstring getInfoToNative(JNIEnv * env, jclass thiz);

/*
 * Java中所定义的Native方法映射表
 */
static JNINativeMethod gJniNativeMethods[] = {
        {"getInfo", "()Ljava/lang/String;", (void*)getInfoToNative},
};

/*
 * 初始化
 */
jint JNI_OnLoad(JavaVM* vm, void* reserved);


#ifdef __cplusplus
}
#endif
#endif

修改JNIUtils.cpp文件:

#include "JNIUtils.h"
#include <stdio.h>
#include <android/log.h>

//JNIEXPORT jstring JNICALL Java_com_zyx_jnidemo_JNIUtils_getInfo(JNIEnv * env, jclass thiz) {
//    __android_log_print(ANDROID_LOG_DEBUG, "zyx", "Hello world from JNI !");
//    //return env->NewStringUTF("Hello world from JNI !");
//
//    jclass clazz = env->FindClass("com/zyx/jnidemo/JNIUtils");
//    if (clazz == NULL) {
//        return env->NewStringUTF("find class error");
//    }
//    jmethodID methodId = env->GetStaticMethodID(clazz, "getJavaInfo", "(Ljava/lang/String;I)Ljava/lang/String;");
//    if(methodId == NULL) {
//        return env->NewStringUTF("find method error");
//    }
//    jstring info = env->NewStringUTF("Hello world from JNI !");
//    jint index = 2;
//    return (jstring)env->CallStaticObjectMethod(clazz, methodId, info, index);
//}

static jstring getInfoToNative(JNIEnv * env, jclass thiz) {
    return env->NewStringUTF("Hello world from JNI !3");
}

jint JNI_OnLoad(JavaVM* vm, void* reserved) {

    JNIEnv* jniEnv = NULL;

    // 从虚拟机中获得JNIEnv,同时指定jni版本
    if (vm->GetEnv((void**) &jniEnv, JNI_VERSION_1_6) != JNI_OK || jniEnv == NULL) {
        return JNI_ERR;
    }

    // 获得Java代码中Natives方法所在类
    jclass clazz = (jniEnv)->FindClass("com/zyx/jnidemo/JNIUtils");
    if (clazz == NULL) {
        return JNI_ERR;
    }

    // 执行注册
    jint nMethods = sizeof(gJniNativeMethods) / sizeof(JNINativeMethod);
    if ((jniEnv)->RegisterNatives(clazz, gJniNativeMethods, nMethods) < 0) {
        return JNI_ERR;
    }

    return JNI_VERSION_1_6;
}

也是非常简单的修改,然后再重新编译程序,运行可见:

 

说明:

JNIUtils.h中定义了两个函数和一个数组:

getInfoToNative                               Native层对应Java层中要注册关联的函数

JNI_OnLoad                                     进行初始化和指定JNI版本

gJniNativeMethods                          是一个JNINativeMethod类型的数组,它是一个函数是映射表,用于关联Java层中定义的Native方法和Native层的相应函数,其中第一个参数是Java中的方法名,第二个参数是方法的签名,第三个参数是Native中函数的指针

JNIUtils.cpp中对两个定义的函数进行了实现,可见在JNI_OnLoad函数内部使用了RegisterNatives函数传入Java中的类和gJniNativeMethods数组来执行动态注册关联两边的方法函数。

 

点击下载示例

 

2018-01-15 14:10:06 jackzhouyu 阅读数 954
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57548 人正在学习 去看看 李宁

So反调试之动态注册native函数

简介及说明

通常我们native开发的步骤是:
1. java层声明native函数
2. javac生class文件以及javah生成native的头文件
3. native中会生成java的class类package路径的函数名
4. 编写native函数实现

缺点

这样的做法,会暴露java层函数的具体位置,破解时,只要ida打开so文件,查看函数名,就能定位出对应Java函数的具体位置;

弥补上诉缺点

使用动态注册java函数,流程如下:

public class NativeUitls {
    public static native String registerFunNative(int number);

    static {
        System.loadLibrary("test");
    }

}
  1. 在Native中声明Java的函数名、参数返回值以及在native映射的函数
/*
* java-native函数映射数组,数组参数意思分别是:
* java层函数名  Java层函数返回值  native层映射的名字
*/
static JNINativeMethod methods[]  = {{"registerFunNative", "(I)Ljava/lang/String;", (void *)funNative}};
jstring funNative(JNIEnv *env, jclass jobj, jint number);
  1. 在JNI_OnLoad()函数中注册registerFunNative这个java函数
JNIEXPORT int JNICALL JNI_OnLoad(JavaVM* vm, void* reserved){
        JNIEnv *env;
        //检查版本号
        if((*vm)->GetEnv( vm , (void **) &env , JNI_VERSION_1_6) != JNI_OK){
                LOGI("load 版本不对");
                return JNI_ERR;
        }
        //查找类
        jclass javaClass = (*env)->FindClass(env, "com/jack/armhello/NativeUitls");
        if(javaClass == NULL){
                LOGI("load not find class");
                return JNI_ERR;
        }
        //注册java方法
        if((*env)->RegisterNatives(env, javaClass, methods, sizeof(methods) / sizeof(methods[0])) < 0){
                LOGI("load register method error");
                return JNI_ERR;
        }

        LOGI("load register succeess");
        return JNI_VERSION_1_6;
}

这里可以做一个反调试思路,我们随机查找我们java任意一个类是否有某个native函数,如果没有就退出,(预先埋好java的native函数)

  1. 在JNI_OnUnLoad()取消函数
 JNIEnv *env;
        if((*vm)->GetEnv(vm, (void **) &env , JNI_VERSION_1_6) != JNI_OK){
                LOGI("unload verson error");
        }

        jclass javaClass = (*env)->FindClass(env, "com/jack/armhello/NativeUitls");
        if(javaClass == NULL){
                LOGI("unload not find class");
        }

        if((*env)->UnregisterNatives(javaClass, javaClass) < 0){
                LOGI("unload unregister faile");
        }

        LOGI("unload unregister succeess");

自此,完成动态注册,这个时候用ida打开so就不会有java种注册的函数了。

破解这种掩藏方法

这种注册一般都是在JNI_OnLoad方法里面完成,我们只要调试这个方法即可,但是也要了解这段注册汇编长什么样子,直接看截图就行了
这里写图片描述

项目地址

2017-07-05 19:08:38 qiyu93422 阅读数 878
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57548 人正在学习 去看看 李宁

我在博客上发表一些我的NDK学习心得,希望对大家能有帮助。 这一篇我们讲述如何动态注册native方法

介绍

首先,之前写的文章中通过一个简单的例子来使用了一下NDK,实现了从Native中调用Java方法。

下面,我们要介绍的是实现动态绑定native方法来破除命名限制。

问题

在静态注册的情况,所有的方法都是有固定的方法名:Java_<包名> <类名> <方法名>,这种情况下,调用一个方法比较繁琐,同时也有命名限制,所以使用动态绑定来解决这个问题。

实践

首先,还是老样子,加载so,定义native方法

public class MyJni {

    static {
        System.loadLibrary("myjni");
    }

    public native void nativePrint();
    public native int nativeCount(int a, int b);

    public MyJni() {
        Log.d("123", "Load MyJni nativePrint & nativeCount");
        nativePrint();
        int a = nativeCount(1,2);
        Log.d("123", a+"");
    }


}

为了在程序初始化的时候进行方法的动态注册,当so被加载时就会调用JNI_OnLoad函数,需要重写JNI_OnLoad方法,在该方法中注册


JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env = NULL;
    jint result = -1;

    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        LOGE("GetEnv failed!");
        return -1;
    }
    //===========================================
    assert(env != NULL);

    if (!registerNatives(env)) // 注册本地方法
    {
        return -1;
    }
    //===========================================
    /* success -- return valid version number */
    result = JNI_VERSION_1_4;

    return result;
}

调用registerNatives进行注册,其中需要指定注册的类信息,这样才能够获取其jclass对象

/*
* 为所有类注册本地方法
*/
static int registerNatives(JNIEnv* env) {
    const char* kClassName = "com/example/qiuyu/testhellojni/MainActivity";//指定要注册的类
    return registerNativeMethods(env, kClassName, gMethods,
                                 sizeof(gMethods) / sizeof(gMethods[0]));
}

调用registerNativeMethods,使用RegisterNatives进行注册


/*
* 为某一个类注册本地方法
*/
static int registerNativeMethods(JNIEnv* env
        , const char* className
        , JNINativeMethod* gMethods, int numMethods) {
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }
    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

需要定义gMethods JNINativeMethod数组,实际就是修改指针指向执行的函数,从而实现方法注册

// 该结构表示一个Java本地方法与native层的c方法的映射  
typedef struct {  
    char *name; // Java本地方法名称  
    char *signature; // Java本地方法签名  
    void *fnPtr; // c函数指针  
} JNINativeMethod;  

/**
* Table of methods associated with a single class.
*/
static JNINativeMethod gMethods[] =
{
    {"nativePrint", "()V", (void*)native_Print },
    {"nativeCount", "(II)I", (void*)native_Count },
};

// 实际执行函数
void native_Print(JNIEnv * env, jobject thiz) {
    LOGE("native_Print");
}

jint native_Count(JNIEnv * env, jobject thiz, jint a, jint b) {
    return a+b;
}

最终执行结果如下:

07-05 04:52:17.739 23202-23202/com.example.qiuyu.testhellojni D/123: Load MyJni nativeSetup
07-05 04:52:17.739 23202-23202/com.example.qiuyu.testhellojni E/jni_thread: native_Print
07-05 04:52:17.739 23202-23202/com.example.qiuyu.testhellojni D/123: 3

注:classname千万不能写错,写错之后会出现java.lang.NoClassDefFoundError错误
注:如果使用ProGuard进行混淆,很可能会找不到native方法,这个要注意

源码

Github : https://github.com/QyMars/AndroidNativeCode

总结

通过动态注册,可以避免硬编码,更加灵活,下面一章学习Native线程使用,之后要完成如何Native中启动一个线程来进行签名校验判断。

2016-07-28 08:26:23 xiangzhihong8 阅读数 2322
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57548 人正在学习 去看看 李宁

前言

或许你知道了jni的简单调用,其实不算什么百度谷歌一大把,虽然这些jni绝大多数情况下都不会让我们安卓工程师来弄,毕竟还是有点难,但是我们还是得打破砂锅知道为什么这样干吧,至少也让我们知道调用流程和数据类型以及处理方法,或许你会有不一样的发现。

今天也给出一个JNI动态注册native方法的例子,如图: 

JNI实现步骤

JNI 开发流程主要分为以下步骤:

  • 编写声明了 native 方法的 Java 类
  • 将 Java 源代码编译成 class 字节码文件
  • 用 javah -jni 命令生成.h头文件(javah 是 jdk 自带的一个命令,-jni 参数表示将 class 中用native 声明的函数生成 JNI 规则的函数)
  • 用本地代码(c/c++)实现.h头文件中的函数
  • 将(c/c++)文件编译成动态库(Windows:*.dll,linux/unix:*.so,mac os x:*.jnilib)
  • 拷贝动态库至本地库目录下,并运行 Java 程序(System.loadLibrary(“xxx”))

我们安卓开发工程师显然只需要编写native的java类,然后clean下编译器知道把我们的java编译成了class文件,但是我们必须知道是调用了javac命令,javah jni命令我们还是得执行,其他的工作就差不多了,不管是什么编译器,反正jni步骤就这样。

JVM 查找 native 方法

JVM 查找 native 方法有两种方式:

  • 按照 JNI 规范的命名规则
  • 调用 JNI 提供的 RegisterNatives 函数,将本地函数注册到 JVM 中。

是不是感到特别的意外,jni还能够利用RegisterNatives 函数查找native方法,其实我也才刚刚知道有这方法,因为要根据包名类名方法名的规范来写是很傻逼的,哈哈,有的人或许觉得这样很直观。

严格按照命名规则实现native方法的调用

我们还是按步骤来说吧,先来解读JNI规范的命名规则:

* 我们先来看下.h文件 *


#include 

#ifndef _Included_com_losileeya_jnimaster_JNIUtils
#define _Included_com_losileeya_jnimaster_JNIUtils
#ifdef __cplusplus
extern  {
#endif

JNIEXPORT jstring JNICALL  Java_com_losileeya_jnimaster_JNIUtils_say
        (JNIEnv *, jclass,jstring);
#ifdef __cplusplus
}
#endif
#endif

我们再来看下Linux 下jni_md.h头文件内容:

#ifndef _JAVASOFT_JNI_MD_H_  
#define _JAVASOFT_JNI_MD_H_  
#define JNIEXPORT  
#define JNIIMPORT  
#define JNICALL  
typedef int jint;  
#ifdef _LP64 /* 64-bit Solaris */  
typedef long jlong;  
#else  
typedef long long jlong;  
#endif  
typedef signed char jbyte;  
#endif  

从上面我们可以看出文件以#ifndef开始然后#endif 结尾,不会C的话是不是看起来有点蛋疼,#号呢代表宏,这里来普及一下宏的使用和定义。

#define 标识符 字符串 
其中,#表示这是一条预处理命令;#define为宏定义命令;“标识符”为宏定义的宏名;“字符串”可以上常数、表达式、格式串等。

举例如下:

#define PI 3.14 
void main()
{
printf(, PI); 
}

条件编译的命令

#ifndef def
语句1
# else
语句2
# endif
表示如果def在前面进行了宏定义那么就编译语句1(语句2不编译),否则编译语句2(语句1不编译)

再看我们.h文件并没有else,所以我们就编译宏定义的本地方法类(com_losileeya_jnimaster_JNIUtils),你突然就会发现我们的宏是我们的native类,然后把包名点类名的点改成了下划线,然后你会发现多了_Included不要多想,就是included关键字加个下划线,这样我们就给本地类进行了宏定义。然后

#ifdef __cplusplus 
extern “C” {#endif

这是说明如果宏定义了c++,并且里面有c我们还是支持c的,并且c代码写extern “C” {}里面。可以看出#endif对应上面的#ifdef-cplusplus,#ifdef-cplusplus对应最后的#endif, #ifdef与#endif总是一一对应的,表明条件编译开始和结束。

JNIEXPORT 和 JNICALL 的作用 
因为安卓是跑在 Linux 下的,所以从 Linux 下的jni_md.h头文件可以看出来,JNIEXPORT 和 JNICALL 是一个空定义,所以在 Linux 下 JNI 函数声明可以省略这两个宏。 
再来看我们的方法:

JNIEXPORT jstring JNICALL  Java_com_losileeya_jnimaster_JNIUtils_say
        (JNIEnv *, jclass,jstring)

函数命名规则为:Java_类全路径_方法名。 
如:Java_com_losileeya_jnimaster_JNIUtils_say,其中Java_是函数的前缀,com_losileeya_jnimaster_JNIUtils是类名,say是方法名,它们之间用 _(下划线) 连接。

  • 第一个参数: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 类型。

如果你需要装逼的话你就可以自己去写.h文件,然后就可以抛弃javah -jni 命令,只需要按照函数命名规则编写相应的函数原型和实现即可(逼就是这么装出来的)

RegisterNatives动态获取本地方法

是不是感觉一个方法的名字太长非常的蛋疼,然后我们呢直接使用,RegisterNatives来自己命名调用native方法,这样是不是感觉好多了。

要实现呢,我们必须重写JNI_OnLoad()方法这样就会当调用 System.loadLibrary(“XXXX”)方法的时候直接来调用JNI_OnLoad(),这样就达到了动态注册实现native方法的作用。

/*
* System.loadLibrary()时调用
* 如果成功返回JNI版本, 失败返回-1
*/
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env = NULL;
    jint result = -1;
    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }
    assert(env != NULL);
    if (!registerNatives(env)) {//注册
        return -1;
    }
    //成功
    result = JNI_VERSION_1_4;
    return result;
}

并且我们需要为类注册本地方法,那样就能方便我们去调用,不多说看方法:


static int registerNatives(JNIEnv* env) {
    return registerNativeMethods(env, JNIREG_CLASS, gMethods,sizeof(gMethods) / sizeof(gMethods[0]));
}

也可以为某一个类注册本地方法


static int registerNativeMethods(JNIEnv* env
        , const char* className
        , JNINativeMethod* gMethods, int numMethods) {
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }
    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

JNINativeMethod 结构体的官方定义

typedef struct {  
  const char* name;  
  const char* signature;  
  void* fnPtr;  
} JNINativeMethod;  
  • 第一个变量name是Java中函数的名字。
  • 第二个变量signature,用字符串是描述了Java中函数的参数和返回值
  • 第三个变量fnPtr是函数指针,指向native函数。前面都要接 (void *)

    第一个变量与第三个变量是对应的,一个是java层方法名,对应着第三个参数的native方法名字(不明白请看后面代码就会清楚了)。

哈哈最后我们就把native方法绑定到JNINativeMethod上我们来看下事例:

static JNINativeMethod gMethods[] = {    
    {,       ,            (void *)com_media_ffmpeg_FFMpegPlayer_setDataSource},    
    {,    ,        (void *)com_media_ffmpeg_FFMpegPlayer_setVideoSurface},    
    {,             ,                              (void *)com_media_ffmpeg_FFMpegPlayer_prepare},    
    {,              ,                              (void *)com_media_ffmpeg_FFMpegPlayer_start},    
    {,               ,                              (void *)com_media_ffmpeg_FFMpegPlayer_stop},    
    {,       ,                              (void *)com_media_ffmpeg_FFMpegPlayer_getVideoWidth},    
    {,      ,                              (void *)com_media_ffmpeg_FFMpegPlayer_getVideoHeight},    
    {,              ,                             (void *)com_media_ffmpeg_FFMpegPlayer_seekTo},    
    {,              ,                              (void *)com_media_ffmpeg_FFMpegPlayer_pause},    
    {,           ,                              (void *)com_media_ffmpeg_FFMpegPlayer_isPlaying},    
    {,  ,                              (void *)com_media_ffmpeg_FFMpegPlayer_getCurrentPosition},    
    {,         ,                              (void *)com_media_ffmpeg_FFMpegPlayer_getDuration},    
    {,            ,                              (void *)com_media_ffmpeg_FFMpegPlayer_release},    
    {,              ,                              (void *)com_media_ffmpeg_FFMpegPlayer_reset},    
    {,  ,                             (void *)com_media_ffmpeg_FFMpegPlayer_setAudioStreamType},    
    {,         ,                              (void *)com_media_ffmpeg_FFMpegPlayer_native_init},    
    {,        ,            (void *)com_media_ffmpeg_FFMpegPlayer_native_setup},    
    {,     ,                              (void *)com_media_ffmpeg_FFMpegPlayer_native_finalize},    
    {, ,                           (void *)com_media_ffmpeg_FFMpegPlayer_native_suspend_resume},    
};    

第一个参数就是我们写的方法,第三个就是.h文件里面的方法,第二个参数显得有点难度,这里会主要去讲。 
主要是第二个参数比较复杂:

括号里面表示参数的类型,括号后面表示返回值。

  • “()” 中的字符表示参数,后面的则代表返回值。例如”()V” 就表示void * Fun();
  • “(II)V” 表示 void Fun(int a, int b);
  • “(II)I” 表示 int sum(int a, int b);

这些字符与函数的参数类型的映射表如下: 
字符 Java类型 C类型 
V void void 
Z jboolean boolean 
I jint int 
J jlong long 
D jdouble double 
F jfloat float 
B jbyte byte 
C jchar char 
S jshort short

数组则以”[“开始,用两个字符表示

[I jintArray int[] 
[F jfloatArray float[] 
[B jbyteArray byte[] 
[C jcharArray char[] 
[S jshortArray short[] 
[D jdoubleArray double[] 
[J jlongArray long[] 
[Z jbooleanArray boolean[] 
如图: 
这里写图片描述

  • 对象类型:以”L”开头,以”;”结尾,中间是用”/” 隔开。如上表第1个
  • 数组类型:以”[“开始。如上表第2个(n维数组的话,则是前面多少个”[“而已,如”[[[D”表示“double[][][]”)
  • 如果Java函数的参数是class,则以”L”开头,以”;”结尾中间是用”/” 隔开的包及类名。而其对应的C函数名的参数则为jobject. 一个例外是String类,其对应的类为jstring

    Ljava/lang/String; String jstring 
    Ljava/net/Socket; Socket jobject 
    这里写图片描述 
    如果JAVA函数位于一个嵌入类,则用作为类名间的分隔符。例如“(Ljava/lang/String;Landroid/os/FileUtilsFileStatus;)Z”

好了,所有 的介绍也完了,那么我们就来实现我们的代码:(果断把h文件删除,看效果) 
JNIUtil.c:







jstring call(JNIEnv* env, jobject thiz)
{
    return (*env)->NewStringUTF(env, );
}
jint sum(JNIEnv* env, jobject jobj,jint num1,jint num2){
    return num1+num2;
}
/**
* 方法对应表
*/
static JNINativeMethod gMethods[] = {
        {, , (void*)call},
        {, , (void*)sum},
};

/*
* 为某一个类注册本地方法
*/
static int registerNativeMethods(JNIEnv* env
        , const char* className
        , JNINativeMethod* gMethods, int numMethods) {
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }
    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
        return JNI_FALSE;
    }

    return JNI_TRUE;
}


/*
* 为所有类注册本地方法
*/
static int registerNatives(JNIEnv* env) {
    return registerNativeMethods(env, JNIREG_CLASS, gMethods,
                                 sizeof(gMethods) / sizeof(gMethods[0]));
}

/*
* System.loadLibrary()时调用
* 如果成功返回JNI版本, 失败返回-1
*/
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env = NULL;
    jint result = -1;
    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }
    assert(env != NULL);
    if (!registerNatives(env)) {//注册
        return -1;
    }
    //成功
    result = JNI_VERSION_1_4;
    return result;
}

代码写完了,主要也是利用findClass来获取方法,从而实现方法的调用。效果重现: 

demo 传送梦:RegisterNatives.rar

JNI数据类型及常用方法(JNI安全手册)

基本类型和本地等效类型表:

这里写图片描述

引用类型: 
这里写图片描述

接口函数表:

const struct JNINativeInterface ... = {
    NULL,
    NULL,
    NULL,
    NULL,
    GetVersion,

    DefineClass,
    FindClass,
    NULL,
    NULL,
    NULL,
    GetSuperclass,
    IsAssignableFrom,
    NULL,

    Throw,
    ThrowNew,
    ExceptionOccurred,
    ExceptionDescribe,
    ExceptionClear,
    FatalError,
    NULL,
    NULL,

    NewGlobalRef,
    DeleteGlobalRef,
    DeleteLocalRef,
    IsSameObject,
    NULL,
    NULL,
    AllocObject,

    NewObject,
    NewObjectV,
    NewObjectA,
    GetObjectClass,

    IsInstanceOf,

    GetMethodID,

    CallObjectMethod,
    CallObjectMethodV,
    CallObjectMethodA,
    CallBooleanMethod,
    CallBooleanMethodV,
    CallBooleanMethodA,
    CallByteMethod,
    CallByteMethodV,
    CallByteMethodA,
    CallCharMethod,
    CallCharMethodV,
    CallCharMethodA,
    CallShortMethod,
    CallShortMethodV,
    CallShortMethodA,
    CallIntMethod,
    CallIntMethodV,
    CallIntMethodA,
    CallLongMethod,
    CallLongMethodV,
    CallLongMethodA,
    CallFloatMethod,
    CallFloatMethodV,
    CallFloatMethodA,
    CallDoubleMethod,
    CallDoubleMethodV,
    CallDoubleMethodA,
    CallVoidMethod,
    CallVoidMethodV,
    CallVoidMethodA,

    CallNonvirtualObjectMethod,
    CallNonvirtualObjectMethodV,
    CallNonvirtualObjectMethodA,
    CallNonvirtualBooleanMethod,
    CallNonvirtualBooleanMethodV,
    CallNonvirtualBooleanMethodA,
    CallNonvirtualByteMethod,
    CallNonvirtualByteMethodV,
    CallNonvirtualByteMethodA,
    CallNonvirtualCharMethod,
    CallNonvirtualCharMethodV,
    CallNonvirtualCharMethodA,
    CallNonvirtualShortMethod,
    CallNonvirtualShortMethodV,
    CallNonvirtualShortMethodA,
    CallNonvirtualIntMethod,
    CallNonvirtualIntMethodV,
    CallNonvirtualIntMethodA,
    CallNonvirtualLongMethod,
    CallNonvirtualLongMethodV,
    CallNonvirtualLongMethodA,
    CallNonvirtualFloatMethod,
    CallNonvirtualFloatMethodV,
    CallNonvirtualFloatMethodA,
    CallNonvirtualDoubleMethod,
    CallNonvirtualDoubleMethodV,
    CallNonvirtualDoubleMethodA,
    CallNonvirtualVoidMethod,
    CallNonvirtualVoidMethodV,
    CallNonvirtualVoidMethodA,

    GetFieldID,

    GetObjectField,
    GetBooleanField,
    GetByteField,
    GetCharField,
    GetShortField,
    GetIntField,
    GetLongField,
    GetFloatField,
    GetDoubleField,
    SetObjectField,
    SetBooleanField,
    SetByteField,
    SetCharField,
    SetShortField,
    SetIntField,
    SetLongField,
    SetFloatField,
    SetDoubleField,
    GetStaticMethodID,
    CallStaticObjectMethod,
    CallStaticObjectMethodV,
    CallStaticObjectMethodA,
    CallStaticBooleanMethod,
    CallStaticBooleanMethodV,
    CallStaticBooleanMethodA,
    CallStaticByteMethod,
    CallStaticByteMethodV,
    CallStaticByteMethodA,
    CallStaticCharMethod,
    CallStaticCharMethodV,
    CallStaticCharMethodA,
    CallStaticShortMethod,
    CallStaticShortMethodV,
    CallStaticShortMethodA,
    CallStaticIntMethod,
    CallStaticIntMethodV,
    CallStaticIntMethodA,
    CallStaticLongMethod,
    CallStaticLongMethodV,
    CallStaticLongMethodA,
    CallStaticFloatMethod,
    CallStaticFloatMethodV,
    CallStaticFloatMethodA,
    CallStaticDoubleMethod,
    CallStaticDoubleMethodV,
    CallStaticDoubleMethodA,
    CallStaticVoidMethod,
    CallStaticVoidMethodV,
    CallStaticVoidMethodA,
    GetStaticFieldID,
    GetStaticObjectField,
    GetStaticBooleanField,
    GetStaticByteField,
    GetStaticCharField,
    GetStaticShortField,
    GetStaticIntField,
    GetStaticLongField,
    GetStaticFloatField,
    GetStaticDoubleField,
    SetStaticObjectField,
    SetStaticBooleanField,
    SetStaticByteField,
    SetStaticCharField,
    SetStaticShortField,
    SetStaticIntField,
    SetStaticLongField,
    SetStaticFloatField,
    SetStaticDoubleField,
    NewString,
    GetStringLength,
    GetStringChars,
    ReleaseStringChars,
    NewStringUTF,
    GetStringUTFLength,
    GetStringUTFChars,
    ReleaseStringUTFChars,
    GetArrayLength,
    NewObjectArray,
    GetObjectArrayElement,
    SetObjectArrayElement,
    NewBooleanArray,
    NewByteArray,
    NewCharArray,
    NewShortArray,
    NewIntArray,
    NewLongArray,
    NewFloatArray,
    NewDoubleArray,
    GetBooleanArrayElements,
    GetByteArrayElements,
    GetCharArrayElements,
    GetShortArrayElements,
    GetIntArrayElements,
    GetLongArrayElements,
    GetFloatArrayElements,
    GetDoubleArrayElements,
    ReleaseBooleanArrayElements,
    ReleaseByteArrayElements,
    ReleaseCharArrayElements,
    ReleaseShortArrayElements,
    ReleaseIntArrayElements,
    ReleaseLongArrayElements,
    ReleaseFloatArrayElements,
    ReleaseDoubleArrayElements,
    GetBooleanArrayRegion,
    GetByteArrayRegion,
    GetCharArrayRegion,
    GetShortArrayRegion,
    GetIntArrayRegion,
    GetLongArrayRegion,
    GetFloatArrayRegion,
    GetDoubleArrayRegion,
    SetBooleanArrayRegion,
    SetByteArrayRegion,
    SetCharArrayRegion,
    SetShortArrayRegion,
    SetIntArrayRegion,
    SetLongArrayRegion,
    SetFloatArrayRegion,
    SetDoubleArrayRegion,
    RegisterNatives,
    UnregisterNatives,
    MonitorEnter,
    MonitorExit,
    GetJavaVM,
};

基本上jni的数据和方法都差不多放这里了,你就可以随便开发了。这个你也可以去看 
jni完全手册

JNI与C/C++数据类型的转换(效率开发)

字符数组与jbyteArray

int byteSize = (int) env->GetArrayLength(jbyteArrayData);  
unsigned char* data = new unsigned char[byteSize + 1];
env->GetByteArrayRegion(jbyteArrayData, 0, byteSize, reinterpret_cast(data));
data[byteSize] = '\0';
jbyte *jb =  (jbyte*) data;   //data是字符数组类型
jbyteArray jarray = env->NewByteArray(byteSize);   //byteSize是字符数组大小
env->SetByteArrayRegion(jarray, 0, byteSize, jb);

字符数组与jstring

char* JstringToChar(JNIEnv* env, jstring jstr) {
    if(jstr == NULL) {
        return NULL;
    }
    char* rtn = NULL;
    jclass clsstring = env->FindClass();
    jstring strencode = env->NewStringUTF();
    jmethodID mid = env->GetMethodID(clsstring, ,
            );
    jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
    jsize alen = env->GetArrayLength(barr);
    jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
    if (alen > 0) {
        rtn = (char*) malloc(alen + 1);
        memcpy(rtn, ba, alen);
        rtn[alen] = 0;
    }
    env->ReleaseByteArrayElements(barr, ba, 0);
    return rtn;
}
jstring StrtoJstring(JNIEnv* env, const char* pat)
{
    jclass strClass = env->FindClass();
    jmethodID ctorID = env->GetMethodID(strClass, , );
    jbyteArray bytes = env->NewByteArray(strlen(pat));
    env->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*)pat);
    jstring encoding = env->NewStringUTF();
    return (jstring)env->NewObject(strClass, ctorID, bytes, encoding);
}

特么最简单的可以直接使用

jstring jstr = env->NewStringUTF(str);

jint与int的互转都可以直接使用强转,如:

jint i = (jint) 1024;

上面的代码你看见了吗,都是env的一级指针来做的,所以是cpp的使用方法,如果你要转成c的那么就把env替换为(*env)好了,具体的方法可能有点小改动(请自行去参考jni手册),报错的地方请自行引入相关的.h文件,估计对你了解jni有更深的了解。

总结

本篇主要介绍了JNI动态注册native方法,并且顺便截了几个jni的图,以及使用的基本数据转换处理,至于实际应用中比如java 调用c,c调用java以及混合调用等我们都需要实践中去处理问题。

没有更多推荐了,返回首页