当前位置:网站首页>Analyze while doing experiments -ndk article -jni uses registernatives for explicit method registration

Analyze while doing experiments -ndk article -jni uses registernatives for explicit method registration

2022-06-11 05:13:00 Kakar

What is? JNI

JNI refer to Java Native Interface, namely JAVA The native interface , Its function is to define JAVA Code and the C/C++ The way the code interacts , Thus making Java To be able to call c/c++ Code , It can also make c/c++ To be able to call java Methods ( Callback ), Facilitate native development , Strictly speaking , It's actually using Java or Kotlin The interaction between bytecode written in programming language and native code , therefore Kotlin And it's also fully supported

JNI stay Android How to use the

To configure c/c++ Compile environment

Since this article does not mainly explain how to configure Native Development environment of , And the official website description is also more detailed , Therefore, the configuration process can refer to the instructions on the official website https://developer.android.google.cn/studio/projects/add-native-code, There is no other explanation here

Be careful , This article will use CMake For the following description

load

JAVA Layers need to pass through System.loadLibrary Method to load so library , By calling... In a static initialization method
JAVA Call mode

JAVA

public class MainNativeInterface {
    
    static {
    
        System.loadLibrary("native-lib");
    }
}

Kotlin

class MainNativeInterfaceKotlin {
    

    companion object {
    
        init {
    
            System.loadLibrary("native-lib")
        }
    }
}

Pay attention to the Kotlin In writing , We used companion object Inside to execute init, In this way, the final translation comes out Java The code will be static Static code block , If init Put it in the outer layer , be Java The code will become called in the constructor of the class . The official recommended practice is to call in the static initialization method , This allows you to load ahead of time

register Native Method

Generally speaking , We will reduce the Native Methods distinguish between static and dynamic registration methods 2 Kind of , But whether it is static registration or dynamic registration ,Native Methods are registered and bound at run time , The way of static registration JNI The layer will find the method name that conforms to the corresponding rules to bind when calling the method , Dynamic registration will be done in JNI When the dynamic library is loaded, all methods are bound , So it can be understood that static registration is an implicit passive registration method , Dynamic registration is the active registration mode of display , Let's take a look at the specific methods of these two methods , You can see that it shows 、 Implicit , Take the initiative 、 The difference between passivity

Static registration - Implicit passivity

The way is through JNI Complete the registration process for us , We just need to configure our method name according to certain method name rules , Let's take a look first Java Layer writing

JAVA

public class MainNativeInterface {
    
    static {
    
        System.loadLibrary("native-lib");
    }

    public native static String stringFromJNI();
}

Kotlin

class MainNativeInterface {
    

    companion object {
    
        init {
    
            System.loadLibrary("native-lib")
        }

        @JvmStatic
        external fun stringFromJNI(): String
    }
}

We define a stringFromJNI Method , The method for native Methods , And here is a static Methods

notes :Kotlin The version corresponds to Java Version of , We used JvmStatic Annotation to indicate that it is a static Methods , And it belongs directly to MainNativeInterface This class of , There may be some students wondering , Why? companion
object no way , You can try to get rid of it , Then look at the translation Java The code of version is clear at a glance (Tools->Kotlin->showKotlinBytecode,
Decompile), original Kotlin Inside companion
object Will create a static Companion class , Then use the method as the internal method of the static class , This is undoubtedly for JNI It will have an impact when binding method queries , We're writing JNI Layer method name , Only the package name will be written + Class name + Method name , And the class name we only write MainNativeInterface, Therefore, the binding will fail
java.lang.NoSuchMethodError: no static or non-static method

And then we were in cpp There is a native-lib.cpp file , In it we define the corresponding native Layer method

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_nativeevaldemo_MainNativeInterface_stringFromJNI(JNIEnv *env, jclass thiz) {
    
    return env->NewStringUTF("native auto search method bind");
}

such , We have completed the implicit passive registration of methods , It is also very convenient to use , Because we declared static Methods , So call the static... Directly native The method can , Of course, you can also use non static methods

JAVA

public class MainNativeInterface {
    
    static {
    
        System.loadLibrary("native-lib");
    }

    public native String stringFromJNI();
}

Kotlin

class MainNativeInterface {
    

    companion object {
    
        init {
    
            System.loadLibrary("native-lib")
        }
    }
    
	external fun stringFromJNI(): String
}

JNI The layer needs to modify the last parameter passed in by the method to be jobject

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_nativeevaldemo_MainNativeInterface_stringFromJNI(JNIEnv *env, jobject thiz) {
    
    return env->NewStringUTF("native auto search method bind");
}

On the use , Non static methods can be a bit laborious , It needs to be done first new Come out of this class Then use it

Explicit active registration

This method requires us to take the initiative to JVM Registration binding Native Method
Java We don't need to change , Let's take a look JNI Layer writing , stay native-lib.cpp Next create a JNI_OnLoad Method

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    
    JNIEnv* env;
    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
    
        return JNI_ERR;
    }

    // Find your class. JNI_OnLoad is called from the correct class loader context for this to work.
    jclass c = env->FindClass("com/example/nativeevaldemo/MainNativeInterface");
    if (c == nullptr) return JNI_ERR;

    // Register your class' native methods.
    static const JNINativeMethod methods[] = {
    
            {
    "stringFromJNI", "()Ljava/lang/String;", (void*) stringFromJNI}
    };
    int rc = env->RegisterNatives(c, methods, sizeof(methods)/sizeof(JNINativeMethod));
    if (rc != JNI_OK) return rc;

    return JNI_VERSION_1_6;
}

static jstring JNICALL
stringFromJNI(JNIEnv *jenv, jclass ths){
    
    return jenv->NewStringUTF("native hand method bind");
}

What we need to pay attention to here is

  • FindClass Inside , We need to use "/" Instead of “.”, To designate our class Complete package path
  • The binding methods must be in your Class You can find , Otherwise, it will report a mistake
  • methods The method signature in the array should correspond to Java Layer method , Specific rules can be found in https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html
  • Finally, return to the desired JNI Version of

In this way, we have completed the binding of methods

Dynamic binding is recommended for these two methods , Because this method is more flexible , And it's more efficient , The only drawback is that you need to write more code than the static way

Callback How to register

Next, let's take an example callback The way

stay Java Layer of MainNativeInterface Class to add a method , And increase callback Of interface


public interface JNICallback {
    
        void result(String message);
    }

public native void callAsyncMethod(JNICallback callback);

JNI layer onLoad Method to dynamically bind methods

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    
    JNIEnv* env;
    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
    
        return JNI_ERR;
    }
    javaVM = vm;
    // Find your class. JNI_OnLoad is called from the correct class loader context for this to work.
    jclass c = env->FindClass("com/example/nativeevaldemo/MainNativeInterface");
    if (c == nullptr) return JNI_ERR;

    // Register your class' native methods.
    static const JNINativeMethod methods[] = {
    
            {
    "stringFromJNI", "()Ljava/lang/String;", (void*) stringFromJNI},
            // Add the method signature , Note that the parameters here are defined by us interface
            {
    "callAsyncMethod", "(Lcom/example/nativeevaldemo/MainNativeInterface$JNICallback;)V", (void*) callAsyncMethod}
    };
    int rc = env->RegisterNatives(c, methods, sizeof(methods)/sizeof(JNINativeMethod));
    if (rc != JNI_OK) return rc;

    return JNI_VERSION_1_6;
}

// Add real binding c++ Method 
static void
callAsyncMethod(JNIEnv *env, jclass thiz, jobject callback) {
    
    jclass clazz = env->GetObjectClass(callback);
    jmethodID jmid = env->GetMethodID(clazz, "result", "(Ljava/lang/String;)V");

    env->CallVoidMethod(callback, jmid, env->NewStringUTF("this is callback result message"));
}

callAsyncMethod In the method , It can be done through the incoming callback Of object obtain class class ( That is, the implementation class of the interface passed in ), And then find class The inside of the class result Methodical Id, And then through callxxxMethod Method call

On use

// Use lambda It will be very simple. 
MainNativeInterface.callAsyncMethod {
    
	Log.d("NativeTestActivity", "result message == $it")
}

call callAsyncMethod Method output
 Insert picture description here
This way callback Just a simple description of the callback method , In real use, it may be necessary to add threads to achieve asynchronous processing or in other .c or .cpp File for callback processing , This is the time to callback Keep it up

Expand

Have any curious friends noticed , What happens if we write both the explicit active registration and the implicit passive registration ?
Here you may as well try , In fact, it is bound by active registration , Because we know that implicit passive registration will find the binding only when the method is called native Method , and , After binding once , Will not bind again , And display the active registration mode onLoad Will execute first , For the first native Method binding

RegisterNatives The method may not be in onLoad Call in method , We can do this to experiment , Let's get rid of onLoad Registration method of

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    
    JNIEnv* env;
    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
    
        return JNI_ERR;
    }
    return JNI_VERSION_1_6;
}

Register a method with implicit passive registration , And use it internally RegisterNatives Method to register and bind another one we show native Method

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_nativeevaldemo_MainNativeInterface_stringFromJNI(JNIEnv *env, jclass thiz) {
    
    // Find your class. JNI_OnLoad is called from the correct class loader context for this to work.
    jclass c = env->FindClass("com/example/nativeevaldemo/MainNativeInterface");

    // Register your class' native methods.
    static const JNINativeMethod methodss[] = {
    
            {
    "stringFromJNI", "()Ljava/lang/String;", (void*) stringFromJNI}
    };
    int rc = env->RegisterNatives(c, methodss, sizeof(methodss)/sizeof(JNINativeMethod));
    return env->NewStringUTF("native auto search method bind");
}
static jstring
stringFromJNI(JNIEnv *jenv, jclass ths){
    
    return jenv->NewStringUTF("native hand method bind");
}

We call the method 2 Time , You will find that the binding of this method will be switched
 Insert picture description here
Isn't it very interesting

原网站

版权声明
本文为[Kakar]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/03/202203020541349203.html