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.