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方式加载函数的流程
-
使用javah
通过javah和待签名的native函数生成.h文件,然后实现这些函数
- 使用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通过这种规则查找函数),动态却不需要
关键在于:
-
定义JNINativeMethod结构map(用作上层native函数和JNI函数名称对应)
-
定义JNI函数
-
实现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[]
- 如果Java函数的参数是class,则以"L"开头,以";"作为类参数的结尾(没有类就不加这个了),中间是用"/" 隔开的包及类名。而其对应的C函数名的参数则为jobject. 一个例外是String类,其对应的类为jstring
Ljava/lang/String; ---》对应java的 String JNI中参数为jstring
Ljava/net/Socket; 对应java的Socket JNI中参数为jobject
-
如果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++调用函数
- 在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!");