At the end of the function dynamic registration article, there is a productivity issue with dynamic registration. When we are in a large project and we need to implement a function in the underlying layer, we declare a native method in the Java layer, and then we have to have a native function in the JNI layer. We know that one of the benefits of dynamic registration is that you can define the name of the function at will. The type of the function can also be obtained from the Javah command. But have we ever wondered if simply adding a JNI function requires a javah command? So how do you write the type of the function quickly? That’s what this article is about.
Example analysis
Let’s use an example to get a feel for what we’re going to learn today.
Suppose you now have a Java class hello.java
public class Hello
{
native int test(String msg);
static native int static_test(String msg);
}
Copy the code
Hello. Java has two native functions, one is static, the other is non-static, so the call method is different.
Now I’m going to use the “dynamic registration” technique, so the first question is, how do I write the functions of the JNI layer? I can write this function out by hand without using the Javah command
jint native_test(JNIEnv * env, jobject thiz, jstring msg);
jint native_static_test(JNIEnv * env, jclass clazz, jstring msg)
Copy the code
Now, for those of you who think I’m bluffing, let me explain how this function is written.
Function name: from this article, we know that “dynamic registration” JNI layer function name can be arbitrarily selected, I put the Java layer native method before a native_ prefix, so that you can see the corresponding relationship at a glance.
Return value: How is the return value determined? There is a type correspondence. In this case, Java int corresponds to Jint in JNI.
First argument: This argument is fixed and is a pointer to JNIEnv.
The second argument: represents a Java object or Java class. If the Java native method is called by an object, then the JNI layer corresponds to a jobject type. If the Java Native method is called by a class, that is, a static native method is called, then the JNI layer corresponds to a JClass type.
The remaining parameters of JNI layer functions are one-to-one corresponding to the native layer functions of Java layer. In this case, the Java String type corresponds to the JNI JString type.
Do we suddenly realize that we need to understand the correspondence between Java types and JNI types?
In the function dynamic registration article, we also need to know the signature of the function, which I can also write quickly
static const JNINativeMethod nativeMethods[] = {
{"static_test"."(Ljava/lang/String;) I", (void *)native_static_test},
{"test"."(Ljava/lang/String;) I", (void *)native_test}
};
Copy the code
The key is to know how to write the type signature of the function.
Read the rest of this article and you’ll understand.
JNI type
Basic types of
For the basic type of equivalence, we can use a table to illustrate
Java Type | Native Type | Description |
---|---|---|
boolean | jboolean | unsigned 8 bit |
byte | jbyte | signed 8 bit |
char | jchar | unsigned 16 bits |
short | jshort | signed 16 bits |
int | jint | signed 32 bits |
long | jlong | signed 64 bits |
float | jfloat | 32 bits |
double | jdouble | 64 bits |
void | void | N/A |
For Java primitives, the corresponding JNI type is simply preceded by a j prefix, which is easy to remember.
Reference types
For the corresponding relationship of reference types, I selected a graph on the official website, as follows.
Let’s remember this picture as a pattern.
First, for array types, there are primitive and reference arrays. An array of primitive types has special JNI objects, such as int[] for jintArray. For reference types, which are not specified in this figure, they actually correspond to jobjectArray, such as Java String[] for JNI jobjectArray.
And then, with the exception of array types, basically everything is represented by jobject, but with three exceptions: String for jString, Class for jclass, and Throwable for jthrowable.
If you are using C, the relationship between these reference types is as follows
/* * Reference types, in C. */
typedef void* jobject;
typedef jobject jclass;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jobjectArray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jobject jthrowable;
typedef jobject jweak;
Copy the code
Jstring, jclass are aliases for jobject, and jobject is a void * notification pointer, and that makes sense, because C is all about notification Pointers, It’s not too much to use a generic pointer type void * to represent all Java types
What about reference type relationships if you’re using C++
/* * Reference types, in C++ */
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jobjectArray : public _jarray {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jthrowable : public _jobject {};
typedef _jobject* jobject;
typedef _jclass* jclass;
typedef _jstring* jstring;
typedef _jarray* jarray;
typedef _jobjectArray* jobjectArray;
typedef _jbooleanArray* jbooleanArray;
typedef _jbyteArray* jbyteArray;
typedef _jcharArray* jcharArray;
typedef _jshortArray* jshortArray;
typedef _jintArray* jintArray;
typedef _jlongArray* jlongArray;
typedef _jfloatArray* jfloatArray;
typedef _jdoubleArray* jdoubleArray;
typedef _jthrowable* jthrowable;
typedef _jobject* jweak;
Copy the code
JNI layer jobject, jintArray, all of these objects are Pointers.
Type signatures
Basic type signature
JNI uses the representation of virtual machine type signatures, so let’s look at basic type signatures first
Type Signature | Java Type |
---|---|
Z | boolean |
B | byte |
C | char |
S | short |
I | int |
J | long |
F | float |
D | double |
Basic types are capitalized with the first letter, except for Boolean, which uses Z, and long, which uses J for its signature. For example, int, which uses I for its type signature.
In addition to the basic type, how to represent the signature of other Java types, the basic format is as follows
Reference type signature
Type Signature | Java Type |
---|---|
L fully-qualified-class ; | fully-qualified-class |
What does that mean? For example, if the full path of String type is java.lang.String, the corresponding signature is Ljava/lang/String. . Note that for the signature of the reference type, the first character of the signature must be L, and the last character must be. .
Array type signature
Arrays also have certain signature requirements, as shown in the following table
Type Signature | Java Type |
---|---|
[type | type[] |
How do you understand that? Take two examples.
Since the signature of int is I, the signature of int[] is [I.
Since the signature of java.lang.String is Ljava/lang/String; , then the signature of String[] is [Ljava/lang/String;
It’s easy to get confused by the examples.
Method type signature
Since Java has method overloading, but JNI does not have the equivalent function overloading, each Java method must have a unique method signature to distinguish method overloading
Type Signature | Java Type |
---|---|
( arg-types ) ret-type | method type |
That’s not easy to understand either. Let’s do an example. Let’s say I have a Java method
String f(int a);
Copy the code
The return type of the f() method is String and the corresponding signature is Ljava/lang/String. , the parameter is int, the corresponding signature is I, then the whole method signature is (I)Ljava/lang/String; .
Handwritten dynamic registration code
After reading the above mentioned types and signatures, combined with the function dynamic registration method described in this article, can you write dynamic registration code?
I’ll end this article by writing out the code for dynamic registration using the example I used at the beginning of the article
#include "hello.h"
#include <android/log.h>
#define LOG_TAG "david"
jint native_test(JNIEnv * env, jobject object,jstring msg) {
const char * p_msg = env->GetStringUTFChars(msg, false);
__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "method = %s, msg = %s", __FUNCTION__, p_msg);
return 0;
}
jint native_static_test(JNIEnv * env, jclass clazz, jstring msg)
{
const char * p_msg = env->GetStringUTFChars(msg, false);
__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "method = %s, msg = %s", __FUNCTION__, p_msg);
return 0;
}
static const JNINativeMethod nativeMethods[] = {
{"static_test"."(Ljava/lang/String;) I", (void *)native_static_test},
{"test"."(Ljava/lang/String;) I", (void *)native_test}
};
static int registerNativeMethods(JNIEnv *env) {
int result = - 1;
jclass class_hello = env->FindClass("com/umx/ndkdemo/Hello");
if (env->RegisterNatives(class_hello, nativeMethods,
sizeof(nativeMethods) / sizeof(nativeMethods[0])) == JNI_OK) {
result = 0;
}
return result;
}
jint JNI_OnLoad(JavaVM * vm, void * reserved) {
JNIEnv* env = NULL;
jint result = - 1;
if (vm->GetEnv((void **)&env, JNI_VERSION_1_1) == JNI_OK) {
if (registerNativeMethods(env) == JNI_OK) { result = JNI_VERSION_1_6; }}return result;
}
Copy the code
This is the end of the function “dynamic registration” technique.