安卓开发之NDK开发基础(一)

暑期实习找到了A的安全开发,提前学习下NDK开发的简单流程。

项目创建

新建一个Empty Activity:
在这里插入图片描述
右键点击 app/src/ 下的 main 目录,然后New > Directory,为目录输入一个名称(例如 cpp)并点击 OK:
在这里插入图片描述
右键点击刚刚创建的cpp目录,然后 New > C/C++ Source File,输入一个名称,例如 test-ndk:
在这里插入图片描述
然后右键点击选中 app ,然后选择 New > File,输入CMakeLists.txt 作为文件名并点击 OK:
在这里插入图片描述
添加命令到 CMakeLists.txt 中:

cmake_minimum_required(VERSION 3.4.1)

add_library( # Sets the name of the library.
             test-ndk

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/test-ndk.cpp )

find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

target_link_libraries( # Specifies the target library.
                       test-ndk

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

include_directories(src/main/cpp/include/)

接着要将 Gradle 关联到原生库,右键app,点击 Link C++ Project with Gradle,配置 CMakeLists.txt 的路径,点击 OK。然后 app目录下的build.gradle文件会自动添加以下代码:

externalNativeBuild {
        cmake {
            path file('CMakeLists.txt')
        }
    }

然后需要配置Javah命令工具,以macos为例,点击左上角打开设置:
在这里插入图片描述
点击+ 配置添加外部工具:
在这里插入图片描述
配置完成后如下:

program:javah的路径
arguments:-classpath . -jni -d $SourcepathEntry$/src/main/cpp $FileClass$
working directory:$SourcepathEntry$

在这里插入图片描述

静态注册

我们使用静态注册来学习一些基础的用法。

JNI访问Java成员变量

编辑MainActivity文件,这里先以静态注册JNI访问Java成员变量为例,编辑 MainActivity:

public class MainActivity extends AppCompatActivity {
    public String showText = "Hello ";
    static {
        System.loadLibrary("test-ndk");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = findViewById(R.id.textview);
        String res = "before: " + showText;
        test();
        res += ", after:" + showText;
        textView.setText(res);
    }
    public native void test();
}

然后右键点击MainActivity,选择弹出框中的 External Tools 的 JavaH 。就会在src/main/java/src/main/cpp目录下生成 com_example_myapplication_MainActivity.h文件,将这个.h文件复制到cpp目录下(应该是可以直接设置生成路径的):
在这里插入图片描述
然后修改test-ndk.cpp代码,遵循以下步骤:

  • 通过env->GetObjectClass(jobject)获取Java 对象的 class 类,返回一个 jclass
  • 调用env->GetFieldID(jclazz, fieldName, signature)得到该实例域(变量)的 id,即 jfieldID;如果变量是静态 static 的,则调用的方法为 GetStaticFieldID
  • 然后如果是要获取变量的话通过调用env->Get{type}Field(jobject, fieldId) 得到该变量的值。其中{type}是变量的类型,例如int、Object等;如果变量是静态 static 的,则调用的方法是GetStatic{type}Field(jclass,fieldId),注意 static 的话, 是使用 jclass 作为参数。如果是要修改的话,就把前面的Get…改为Set{type}Field(jobject, fieldId, “新的字符串”),修改静态变量为SetStatic{type}Field(jclass, fieldId, “新的字符串”);

代码如下:

#include "jni.h"
#include "com_demo_ndktest_MainActivity.h"

extern "C" JNIEXPORT void JNICALL Java_com_demo_ndktest_MainActivity_test
        (JNIEnv *env , jobject obj){
        jclass jcls = env->GetObjectClass(obj);
        jfieldID  jfid = env->GetFieldID(jcls,"showText", "Ljava/lang/String;");
        jstring js = env->NewStringUTF("Hello world");
        env->SetObjectField(obj,jfid,js);
}

然后编译运行即可:
在这里插入图片描述

JNI访问Java静态变量

如果是静态注册JNI访问Java静态变量的话:

public class MainActivity extends AppCompatActivity {
    public static String static_String = "static";
    static {
        System.loadLibrary("test-ndk");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = findViewById(R.id.textview);
        String res = "before: " + static_String;
        test();
        res += ", after:" + static_String;
        textView.setText(res);
    }
    public native void test();
}

cpp就是把非静态的改为GetStaticFieldID、SetStaticObjectField,编译运行后:
在这里插入图片描述

JNI访问Java非静态方法

如果是静态注册JNI访问Java非静态方法,可以看到MainActivity方法中是没有调用过getName()方法的,我们在so中调用,也就是test()方法中调用:

public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("test-ndk");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = findViewById(R.id.textview);
        textView.setText(test());
    }
    public String getName(String name){
        return "ndk"+name;
    }

    public native String test();
}

重新javah后,编辑cpp文件如下:

#include "jni.h"
#include "com_demo_ndktest_MainActivity.h"

extern "C" JNIEXPORT jstring JNICALL Java_com_demo_ndktest_MainActivity_test
        (JNIEnv *env, jobject obj){
    jclass jcls = env->GetObjectClass(obj);
    jmethodID jmid = env->GetMethodID(jcls,"getName", "(Ljava/lang/String;)Ljava/lang/String;");
    jstring js = env->NewStringUTF("ndk");
    jobject job = env->CallObjectMethod(obj,jmid,js);
    return static_cast<jstring >(job);
}

然后编译运行:
在这里插入图片描述

JNI访问Java静态方法

如果是静态注册JNI访问Java静态方法的话,修改MainActivity:

public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("test-ndk");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = findViewById(R.id.textview);
        textView.setText(test(100)+"");
    }
    public static int getValue(int max){
        return new Random().nextInt(max);
    }
    public native int test(int max);
}

然后编辑cpp文件:

#include "jni.h"
#include "com_demo_ndktest_MainActivity.h"‘

extern "C" JNIEXPORT jint JNICALL Java_com_demo_ndktest_MainActivity_test
        (JNIEnv *env, jobject obj,jint max){

    jclass jcls = env->GetObjectClass(obj);
    jmethodID methodID = env->GetStaticMethodID(jcls,"getValue", "(I)I");
    jint res = env->CallStaticIntMethod(jcls, methodID, max);
    return res;
}

编辑运行如下:
在这里插入图片描述

JNI访问Java构造方法

这里以访问Date类的构造方法为例,修改MainActivity:

public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("test-ndk");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = findViewById(R.id.textview);
        textView.setText(test().toString());
    }
    public native Date test();
}

修改test-ndk.cpp文件:

#include "jni.h"
#include "com_demo_ndktest_MainActivity.h"

extern "C" JNIEXPORT jobject JNICALL Java_com_demo_ndktest_MainActivity_test
        (JNIEnv *env, jobject obj){
      jclass jcls = env->FindClass("java/util/Date");//反射获取
      jmethodID jmid = env->GetMethodID(jcls,"<init>", "()V");
      jobject jobj = env->NewObject(jcls,jmid);
      return  jobj;
}

编译运行后结果如下:
在这里插入图片描述

JNI操作Java数组

这里以创建数组以及对数组进行排序为例,修改MainActivity:

public class MainActivity extends AppCompatActivity {
    public String before = "";
    public String after = "";

    static {
        System.loadLibrary("test-ndk");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = findViewById(R.id.textview);
        TextView textView2 = findViewById(R.id.textview2);
        test();
        textView.setText(before);
        textView2.setText(after);
    }

    public void test(){
        int[] array = getIntArray(10);
        for(int i:array){
            before += i+ " ";
        }
        sortIntArray(array);
        for(int i:array){
            after += i+ " ";
        }
    }

    public native int[] getIntArray(int len);
    public native void sortIntArray(int[] arr);

}

修改cpp文件:

#include "jni.h"
#include "com_demo_ndktest_MainActivity.h"
#include "random"

/*
 * Class:     com_demo_ndktest_MainActivity
 * Method:    getIntArray
 * Signature: (I)[I
 */
extern "C" JNIEXPORT jintArray JNICALL Java_com_demo_ndktest_MainActivity_getIntArray
        (JNIEnv *env, jobject obj, jint len){
      jintArray array = env->NewIntArray(len);
      jint *arrPtr = env->GetIntArrayElements(array, nullptr);
      jint *start = arrPtr;
      for(;start<arrPtr+len;start++){
            *start = static_cast<jint>(random()%100);
      }
      //C中操作同步到java,并释放资源
      env->ReleaseIntArrayElements(array,arrPtr,0);
      return array;
}

/*
 * Class:     com_demo_ndktest_MainActivity
 * Method:    sortIntArray
 * Signature: ([I)V
 */
int compare(const void *a,const void *b){
      return *(int *) a - *(int *) b;
}
extern "C" JNIEXPORT void JNICALL Java_com_demo_ndktest_MainActivity_sortIntArray
        (JNIEnv *env, jobject obj, jintArray intArr){
      //获取起始指针
      jint *arrPtr = env->GetIntArrayElements(intArr,nullptr);
      //获取数组长度
      jint len = env->GetArrayLength(intArr);
      qsort(arrPtr,len, sizeof(jint),compare);
      env->ReleaseIntArrayElements(intArr,arrPtr,0);
}

编译运行后结果如下:
在这里插入图片描述

JNI遍历文件夹

首先封装一个打印相关的头文件LogUtils.h:

#ifndef _LOG_UTILS_H_
#define _LOG_UTILS_H_

#define DEBUG // 可以通过 CmakeLists.txt 等方式来定义在这个宏,实现动态打开和关闭LOG

// Windows 和 Linux 这两个宏是在 CMakeLists.txt 通过 ADD_DEFINITIONS 定义的
#ifdef Windows
#define __FILENAME__ (strrchr(__FILE__, '\\') + 1) // Windows下文件目录层级是'\\'
#elif Linux
#define __FILENAME__ (strrchr(__FILE__, '/') + 1) // Linux下文件目录层级是'/'
#else
#define __FILENAME__ (strrchr(__FILE__, '/') + 1) // 默认使用这种方式
#endif

#ifdef DEBUG
#define TAG "JNI"
#define LOGV(format, ...) __android_log_print(ANDROID_LOG_VERBOSE, TAG,\
        "[%s][%d]: " format, __FILENAME__,  __LINE__, ##__VA_ARGS__);
#define LOGD(format, ...) __android_log_print(ANDROID_LOG_DEBUG, TAG,\
        "[%s][%d]: " format, __FILENAME__,  __LINE__, ##__VA_ARGS__);
#define LOGI(format, ...) __android_log_print(ANDROID_LOG_INFO, TAG,\
        "[%s][%d]: " format, __FILENAME__,  __LINE__, ##__VA_ARGS__);
#define LOGW(format, ...) __android_log_print(ANDROID_LOG_WARN, TAG,\
        "[%s][%d]: " format, __FILENAME__,  __LINE__, ##__VA_ARGS__);
#define LOGE(format, ...) __android_log_print(ANDROID_LOG_ERROR, TAG,\
        "[%s][%d]: " format, __FILENAME__,  __LINE__, ##__VA_ARGS__);
#else
#define LOGV(format, ...);
#define LOGD(format, ...);
#define LOGI(format, ...);
#define LOGW(format, ...);
#define LOGE(format, ...);
#endif // DEBUG

#endif // _LOG_UTILS_H

修改MainActivity代码:

public class MainActivity extends AppCompatActivity {

    static {
        System.loadLibrary("test-ndk");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        test();
    }

    public void test(){
        listDirFIle("system/app/");
    }
    
    public native void listDirFIle(String dirPath);
}

修改cpp文件代码:

#include "jni.h"
#include "com_demo_ndktest_MainActivity.h"
#include "string"
#include "dirent.h"
#include "android/log.h"
#include "LogUtils.h"

const int PATH_MAX_LENGTH = 256;

extern "C" JNIEXPORT void JNICALL Java_com_demo_ndktest_MainActivity_listDirFIle
        (JNIEnv *env, jobject instance, jstring dirPath_){
    if (dirPath_ == nullptr) {
        LOGE("dirPath is null!");
        return;
    }
    const char *dirPath = env->GetStringUTFChars(dirPath_, nullptr);
    //长度判断
    if (strlen(dirPath) == 0) {
        LOGE("dirPath length is 0!");
        return;
    }
    //打开文件夹读取流
    DIR *dir = opendir(dirPath);
    if (nullptr == dir) {
        LOGE("can not open dir, check path or permission!")
        return;
    }

    struct dirent *file;
    while ((file = readdir(dir)) != nullptr) {
        //判断是不是 . 或者 .. 文件夹
        if (strcmp(file->d_name, ".") == 0 || strcmp(file->d_name, "..") == 0) {
            continue;
        }

        if (file->d_type == DT_DIR) {
            //是文件夹则遍历
            //构建文件夹路径
            char *path = new char[PATH_MAX_LENGTH];
            memset(path, 0, PATH_MAX_LENGTH);
            strcpy(path, dirPath);
            strcat(path, "/");
            strcat(path, file->d_name);
            jstring tDir = env->NewStringUTF(path);
            //递归遍历
            Java_com_demo_ndktest_MainActivity_listDirFIle(env, instance, tDir);
            //释放文件夹路径内存
            free(path);
        } else {
            //打印文件名
            LOGD("### %s/%s", dirPath, file->d_name);
        }
    }

    //关闭读取流
    closedir(dir);
    env->ReleaseStringUTFChars(dirPath_, dirPath);
}

编译运行后:
在这里插入图片描述

调用父类方法

修改MainActivity:

public class MainActivity extends AppCompatActivity {

    static {
        System.loadLibrary("test-ndk");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = findViewById(R.id.textview);
        childJni cj = new childJni();
        textView.setText(cj.test());
    }
    public class SuperJni{
        public String hello(String name){
            return "Hello " + name;
        }
    }
    public  class  childJni extends SuperJni{
        public native String test();
    }
}

修改cpp:

extern "C" JNIEXPORT jstring JNICALL Java_com_demo_ndktest_MainActivity_00024childJni_test
        (JNIEnv *env, jobject obj){
       //需要通过反射FindClass获取父类实体类,FindClass需要传入完整的类名。而GetObjectClass只需一个jobject饮用即可
       jclass jcls = env->FindClass("com/demo/ndktest/MainActivity$SuperJni");
       if(jcls == nullptr){
           return env->NewStringUTF("error");
       }
       jmethodID jmid = env->GetMethodID(jcls,"hello", "(Ljava/lang/String;)Ljava/lang/String;");
       jstring js = env->NewStringUTF("World");
       //调用父类的方法是 CallNonvirtual{type}Method 函数
       return static_cast<jstring>(env->CallNonvirtualObjectMethod(obj, jcls, jmid, js));
}

编译运行后结果如下:
在这里插入图片描述

自定义对象参数的传递

新建Person类:

public class Person {
    private String name;
    private int age;

    public Person(){}
    public Person(int age,String name){
        this.age = age;
        this.name = name;
    }
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person :{ name: "+name+", age: "+age+"}";
    }
}

修改MainActivity代码:

public class MainActivity extends AppCompatActivity {

    static {
        System.loadLibrary("test-ndk");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = findViewById(R.id.textview);
        textView.setText(test(new Person()).toString());
    }

    public native Person test(Person person);
}

修改cpp代码:

extern "C" JNIEXPORT jobject JNICALL Java_com_demo_ndktest_MainActivity_test
        (JNIEnv *env, jobject obj, jobject person){
      jclass jcls = env->FindClass("com/demo/ndktest/Person");
      jmethodID jmid = env->GetMethodID(jcls,"<init>", "(ILjava/lang/String;)V");
      if(jmid == NULL){
          return env->NewStringUTF("Error");
      }
      jstring name = env->NewStringUTF("irirs");
      return env->NewObject(jcls,jmid,21,name);
}

编译运行结果如下:
在这里插入图片描述

自定义对象的集合参数的传递

修改MainActivity:

public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("test-ndk");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = findViewById(R.id.textview);
        TextView textView2 = findViewById(R.id.textview2);
        ArrayList<Person> arrayList = new ArrayList<>();
        for(int i=0;i<3;i++){
            arrayList.add(new Person(10+i,"iris"));
        }
        textView.setText("before:" + arrayList.toString());
        textView2.setText("after:" + test(arrayList).toString());
    }
    public native ArrayList<Person> test(ArrayList<Person> persons);
}

修改cpp:

extern "C" JNIEXPORT jobject JNICALL Java_com_demo_ndktest_MainActivity_test
        (JNIEnv *env, jobject obj, jobject personArrayList){
        jclass jcls = env->GetObjectClass(personArrayList);// //通过参数获取 ArrayList 对象的 class
        jmethodID jmid = env->GetMethodID(jcls,"<init>", "()V");//获取无参构造函数
        jobject arrayList = env->NewObject(jcls,jmid);//new一个 ArrayList 对象
       
        jmethodID addid = env->GetMethodID(jcls,"add", "(Ljava/lang/Object;)Z");

        jclass pjcls = env->FindClass("com/demo/ndktest/Person");
        jmethodID pjmid = env->GetMethodID(pjcls,"<init>", "(ILjava/lang/String;)V");
        jint i = 0;
        for(;i<3;i++){
            jstring name = env->NewStringUTF("refrain");
            jobject person = env->NewObject(pjcls,pjmid,20+i,name);
            env->CallBooleanMethod(arrayList,addid,person);
        }
        return arrayList;
}

编译运行后效果如下:
在这里插入图片描述

动态注册

可以看到静态注册java层方法和native层方法名称是相似的,安全性不高。并且函数名太长,文件、类名、变量或方法重构时,需要重新修改头文件或 C/C++ 内容代码。动态注册可以解决这个问题,其原理是直接告诉 native 方法其在JNI 中对应函数的指针。通过使用 JNINativeMethod 结构来保存 Java native 方法和 JNI 函数关联关系。具体步骤如下:
先编写 Java 的 native 方法;

  • 编写 JNI 函数的实现(函数名任意)
  • 利用结构体 JNINativeMethod 保存Java native方法和 JNI函数的对应关系;
  • 利用registerNatives(JNIEnv* env)注册类的所有本地方法;
  • 在JNI_OnLoad 方法中调用注册方法;
  • 在Java中通过System.loadLibrary加载完JNI动态库之后,会自动调用JNI_OnLoad函数,完成动态注册;

先修改Mainactivity:

public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("test-ndk");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = findViewById(R.id.textview);
        textView.setText(test());
    }
    public native String test();
}

动态注册不再需要javah进行处理,直接编辑cpp文件:

#include "jni.h"
#include "string"

extern "C" {
       static jstring jni_string(JNIEnv *env, jobject obj){
            jstring res = env->NewStringUTF("hello c++");
            return res;
       }
       static JNINativeMethod jNM_table[] = {
               {"test", "()Ljava/lang/String;",(void *)jni_string}//还有其他方法的话,直接后面,补就行
       };

       static int registerNativeMethods(JNIEnv *env){
            jclass clazz = env->FindClass("com/demo/ndktest/MainActivity");
            if(clazz == NULL){
                return JNI_ERR;
            }
            if(env->RegisterNatives(clazz,jNM_table, sizeof(jNM_table) / sizeof(jNM_table[0]))<0){
                return JNI_ERR;
            }
            return JNI_OK;
       }

       jint JNI_OnLoad(JavaVM *vm, void *reserved){
           JNIEnv *env = NULL;
           if(vm->GetEnv((void **) &env,JNI_VERSION_1_4)!=JNI_OK){
               return -1;
           }
           registerNativeMethods(env);
           return JNI_VERSION_1_4;
       }
}

编辑运行后结果如下:
在这里插入图片描述

多个类的动态注册

新建JNIAdd类,里面有个静态的native方法:

public class JNIAdd {
    public static native int add(int x, int y);
}

修改MainActivity:

public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("test-ndk");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = findViewById(R.id.textview);
        TextView textView2 = findViewById(R.id.textview2);
        TextView textView3 = findViewById(R.id.textview3);

        textView.setText(stringFromJNI());
        textView2.setText(setString("Hello"));
        textView3.setText(JNIAdd.add(5,3)+"");
    }
    public native String stringFromJNI();
    public native String setString(String str);
}

修改cpp:

#include "jni.h"
#include "string"

extern "C" {
       static jstring jni_string(JNIEnv *env, jobject obj){//非static native方法的第二个参数为jobject
               return env->NewStringUTF("hello ndk");
       }

       static jint sum(JNIEnv *env,jclass clzz,jint x,jint y){//static native方法的第二个参数为jclass
               return x+y;
       }

       static jstring jni_setstr(JNIEnv *env, jobject obj, jstring str){
               char *dirPath = const_cast<char *>(env->GetStringUTFChars(str, nullptr));
               strcat(dirPath," world!");
               jstring res = env->NewStringUTF(dirPath);
               return res;
       }

       static JNINativeMethod jNM_MainActivity[] = {
               {"stringFromJNI","()Ljava/lang/String;",(void *) jni_string},
               {"setString","(Ljava/lang/String;)Ljava/lang/String;",(void *) jni_setstr},
       };

       static JNINativeMethod JNM_JNIAdd[] = {
               {"add","(II)I",(void *) sum}
       };

       static int registerNativeMethods(JNIEnv *env){
            jclass clazz = env->FindClass("com/demo/ndktest/MainActivity");
            if(clazz == NULL){
                return JNI_ERR;
            }
            if(env->RegisterNatives(clazz,jNM_MainActivity, sizeof(jNM_MainActivity) / sizeof(jNM_MainActivity[0]))<0){
                return JNI_ERR;
            }
            return JNI_OK;
       }
        static int registerNativeMethods_JNIAdd(JNIEnv *env){
            jclass clazz = env->FindClass("com/demo/ndktest/JNIAdd");
            if(clazz == NULL){
                return JNI_ERR;
            }
            if(env->RegisterNatives(clazz,JNM_JNIAdd, sizeof(JNM_JNIAdd) / sizeof(JNM_JNIAdd[0]))<0){
                return JNI_ERR;
            }
            return JNI_OK;
        }

       jint JNI_OnLoad(JavaVM *vm, void *reserved){
           JNIEnv *env = NULL;
           if(vm->GetEnv((void **) &env,JNI_VERSION_1_4)!=JNI_OK){
               return -1;
           }
           registerNativeMethods(env);
           registerNativeMethods_JNIAdd(env);
           return JNI_VERSION_1_4;
       }
}

运行结果如下:
在这里插入图片描述

参考:

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值