JNI的静态注册和动态注册

JNI的静态注册和动态注册:

前提

​ JNI是java的东西,Android只是使用优化!!

参考感谢

https://blog.csdn.net/afei__/article/details/81031965

https://blog.csdn.net/u013365635/article/details/83015699

https://blog.csdn.net/bigapple88/article/details/6756204

1. JNI方式加载函数的流程

  1. 使用javah

    通过javah和待签名的native函数生成.h文件,然后实现这些函数

    1. 使用JNI_OnLoad方法

    当加载System.loadLibrary()函数的时候,会先调用JNI_OnLoad方法(所以这也就是动态注册的根本)

    如果没有定义JNI_OnLoad方法进行函数签名注册,而是静态方式,就会等到native函数具体调用处去所有的加载so中查询此函数

2. 静态 VS 动态

​ 静态:运行的时候 去so寻找 函数

​ 动态: 编译加载so的时候就把函数进行注册,之后的调用可以直接使用

3. 静态的实现

​ javah的什么就不说了,直接可以看https://www.cnblogs.com/zero-waring/p/14022106.html

​ 适用处:
​ 较少的函数

​ 不要求效率

4. 动态注册的实现

​ java上层的调用和静态注册相同,只不过是JNI的实现函数有了区别

​ 在静态方式中必须遵守 命名规则(因为JVM通过这种规则查找函数),动态却不需要

​ 关键在于:

  1. 定义JNINativeMethod结构map(用作上层native函数和JNI函数名称对应)

  2. 定义JNI函数

  3. 实现JNI_OnLoad函数(在这里面进行注册)

    开始实现,略过so的打包生成

    native的函数是:

        native public void getString();
    

    JNI里面的函数实现:

    #ifdef __cplusplus
    extern "C" {
    #endif
    
    //定义你要把你的JNI函数注册到哪个类中的native函数
    static const char *classPathName = "com/company/Main";
    
    //具体的JNI实现函数
    void getString(JNIEnv * env, jclass clazz) {
        std::cout << "im getString" << std::endl;
    }
    
    //我们上面说的第一步,将上层java native函数和JNI实现函数注册,第一个参数就是native名称
    //第二个参数是签名,第三个参数是实现的函数地址
    static JNINativeMethod methods[] = {
        {"getString", "()V", (void *)getString},
    };
    
    //实现注册的函数
    static int registerNativeMethods(JNIEnv* env, const char* className,
        JNINativeMethod* gMethods, int numMethods)
    {
        jclass clazz;
    	
    	//注册函数需要和对应类挂钩,这里先取地对应类
        clazz = env->FindClass(className);
        if (clazz == NULL)
            return JNI_FALSE;
    
        if (env->RegisterNatives(clazz, gMethods, numMethods) < 0)
        {
    	//LOGE("register nativers error");
            return JNI_FALSE;
        }
    
        return JNI_TRUE;
    }
    
    //实际调用注册的函数
    static int registerNatives(JNIEnv* env)
    {
      if (!registerNativeMethods(env, classPathName,
                     methods, sizeof(methods) / sizeof(methods[0]))) {
        return JNI_FALSE;
      }
      return JNI_TRUE;
    }
    
    //真正开始被执行的函数
    jint JNI_OnLoad(JavaVM* vm, void* reserved)
    {
        JNIEnv* env = NULL;
        jint result = -1;
    
        std::cout << "Entering JNI_OnLoad" << std::endl;
    
        if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
            return -1;
        }
    
        if ( env == NULL ) return -1;
        if (!registerNatives(env))
            return -1;
    	
    	//这里要返回否则会错误
        return JNI_VERSION_1_4;
    }
    
    #ifdef __cplusplus
    }
    #endif
    

5. registerNativeMethods注册

​ 通过上面的例子可以看出,大体流程就是那样,主要就是你的注册函数

​ 看一下registerNativeMethods原型:

typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;

学过c或者c++的就可以很明白看出这个结构,第一个就是 java调用层native侧的名称,第二个代表的参数和返回值,第三个代表函数实现的地址

主要说第二个参数:

​ 这是有map表对应的,是和函数返回值参数一一对应的

​ "()" 中的字符表示参数,后面的则代表返回值。例如"()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[]
[O    jObjectArray object[]
  1. 如果Java函数的参数是class,则以"L"开头,以";"作为类参数的结尾(没有类就不加这个了),中间是用"/" 隔开的包及类名。而其对应的C函数名的参数则为jobject. 一个例外是String类,其对应的类为jstring
Ljava/lang/String; ---》对应java的 String JNI中参数为jstring
Ljava/net/Socket; 对应java的Socket JNI中参数为jobject
  1. 如果JAVA函数位于一个嵌入类,则用$作为类名间的分隔符。

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

总结:(函数参数)返回值

6. 复杂的注册1

-->传入数组参数,修改第一个数组的值,返回一个新的字符串

native实现

native public String getString(String[] nameList, int value);

JNI实现

#include <jni.h>       // JNI header provided by JDK
#include <iostream>    // C++ standard IO header
#include <string>    // C++ standard IO header
#ifdef __cplusplus
extern "C" {
#endif

static const char *classPathName = "com/company/Main";

jstring getString(JNIEnv * env, jobject thisObj, jobjectArray nameList, jint value) {
    jint arrLen = env->GetArrayLength(nameList);
    std::cout << "getString" << " arrlen is " << arrLen << std::endl;
        std::cout << "getString" << " value is " << value << std::endl;
    jstring name = (jstring)env->GetObjectArrayElement(nameList, 0);
    const char *inCStr = env->GetStringUTFChars(name, NULL);
    std::cout << inCStr << std::endl;
    env->ReleaseStringUTFChars(name, inCStr);  // release resources
   if (NULL == inCStr) return NULL;
   jstring newName = env->NewStringUTF(std::string("new").c_str());
   env->SetObjectArrayElement(nameList, 0, newName);
   return env->NewStringUTF(std::string("over").c_str());
}

static JNINativeMethod methods[] = {
    {"getString", "([Ljava/lang/String;I)Ljava/lang/String;", (void *)getString},
};
static int registerNativeMethods(JNIEnv* env, const char* className,
    JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;

    clazz = env->FindClass(className);
    if (clazz == NULL)
        return JNI_FALSE;

    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0)
    {
	//LOGE("register nativers error");
        return JNI_FALSE;
    }

    return JNI_TRUE;
}


static int registerNatives(JNIEnv* env)
{
  if (!registerNativeMethods(env, classPathName,
                 methods, sizeof(methods) / sizeof(methods[0]))) {
    return JNI_FALSE;
  }
  return JNI_TRUE;
}

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

    std::cout << "Entering JNI_OnLoad" << std::endl;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }

    if ( env == NULL ) return -1;
    if (!registerNatives(env))
        return -1;

    return JNI_VERSION_1_4;
}


#ifdef __cplusplus
}
#endif

7. 复杂的注册2

​ 传入byte数组 将input数组赋值给output

native函数

    native public boolean getInvalid(byte[] input, int w, int h, byte[] output);

JNI函数

#include <jni.h>       // JNI header provided by JDK
#include <iostream>    // C++ standard IO header
#include <string>    // C++ standard IO header
#include <cstdio>    // C++ standard IO header
#ifdef __cplusplus
extern "C" {
#endif

static const char *classPathName = "com/company/Main";

jstring getString(JNIEnv * env, jobject thisObj, jobjectArray nameList, jint value) {
    jint arrLen = env->GetArrayLength(nameList);
    std::cout << "getString" << " arrlen is " << arrLen << std::endl;
        std::cout << "getString" << " value is " << value << std::endl;
    jstring name = (jstring)env->GetObjectArrayElement(nameList, 0);
    const char *inCStr = env->GetStringUTFChars(name, NULL);
    std::cout << inCStr << std::endl;
    env->ReleaseStringUTFChars(name, inCStr);  // release resources
   if (NULL == inCStr) return NULL;
   jstring newName = env->NewStringUTF(std::string("new").c_str());
   env->SetObjectArrayElement(nameList, 0, newName);
   return env->NewStringUTF(std::string("over").c_str());
}

jboolean getInvalid(JNIEnv * env, jobject thisObj, jbyteArray input, jint w, jint h, jbyteArray output) {
    jbyte * arrayinput= env->GetByteArrayElements(input,NULL);
    if (NULL == arrayinput) return (jboolean)false;
    jsize length = env->GetArrayLength(input);
            std::cout << "length is "<< length << std::endl;
    for(int i = 0; i  < length; i++) {
        printf("input value is %c\n", arrayinput[i]);
        printf("input value\n");
    }
    jbyte * arrayoutput= env->GetByteArrayElements(output,NULL);
    if (NULL == arrayoutput) return (jboolean)false;
    for ( int i = 0; i < length; i++) {
     arrayoutput[i] = arrayinput[i];
    }
    env->ReleaseByteArrayElements(input, arrayinput, 0);
    env->ReleaseByteArrayElements(output, arrayoutput, 0);
    return (jboolean)true;
}


static JNINativeMethod methods[] = {
    {"getString", "([Ljava/lang/String;I)Ljava/lang/String;", (void *)getString},
    {"getInvalid", "([BII[B)Z", (void *)getInvalid},
};
static int registerNativeMethods(JNIEnv* env, const char* className,
    JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;

    clazz = env->FindClass(className);
    if (clazz == NULL)
        return JNI_FALSE;

    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0)
    {
	//LOGE("register nativers error");
        return JNI_FALSE;
    }

    return JNI_TRUE;
}


static int registerNatives(JNIEnv* env)
{
  if (!registerNativeMethods(env, classPathName,
                 methods, sizeof(methods) / sizeof(methods[0]))) {
    return JNI_FALSE;
  }
  return JNI_TRUE;
}

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

    std::cout << "Entering JNI_OnLoad" << std::endl;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }

    if ( env == NULL ) return -1;
    if (!registerNatives(env))
        return -1;

    return JNI_VERSION_1_4;
}


#ifdef __cplusplus
}
#endif

8. 有的时候注册的签名实在不会

​ 如何查看类中的方法的签名:
​ javap -s -p Test.class

​ 这里需要将.java文件转成.class文件,然后通过javap命令方能查看签名。

9. 说一下JNI 中 c/c++调用函数

  1. 在C的定义中,env是一个两级指针,而在C++的定义中,env是个一级指针。C形式需要对env指针进行双重deferencing,而且须将env作为第一个参数传给jni函数,举个简单的例子:

eg:

  • 对于XXX.c:JNI 函数调用由“(*env)->”作前缀,目的是为了取出函数指针所引用的值。

    return (*env)->NewStringUTF(env,"HelloJNI!");  
    
  • 对于XXX.cpp:JNIEnv 类拥有处理函数指针查找的内联成员函数。

    return env->NewStringUTF("HelloJNI!"); 
    
posted @ 2021-03-27 14:47  make_wheels  阅读(624)  评论(0编辑  收藏  举报