【JNI编程】实现JNI的两种方法

通常来说我们使用JNI,Java层中对应的接口方法在本地代码中有对应的方法实现。

一、通过动态链接器根据条目的名称解析

通过动态链接器根据条目的名称解析条目。本地方法名称由以下组件连接而成:

  • 前缀Java_
  • 一个全限定类名
  • 下划线(" _ ")分隔符
  • 一个方法名
  • 对于重载的本地方法,两个下划线(“__”)后面跟着参数签名

VM检查方法名与驻留在本地库中的方法是否匹配。VM首先查找短名称;即没有参数签名的名称。然后,它查找具有参数签名的长名称。只有在本地方法被另一个本地方法重载时,程序员才需要使用长名称。但是,如果本地方法与非本地方法具有相同的名称,则这不是问题。非本机方法(Java方法)不驻留在本地库中。

示例代码通过Android Studio编译即可。

1.1 方法没有重载版本

package com.example.ndk.ndkdemo;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}

Java层stringFromJNI()方法对应的实现实际上就是本地库中以下方法(Java_com_example_ndk_ndkdemo_MainActivity_stringFromJNI(…)):

#include <jni.h>
#include <string>

jstring NewStringUTF(JNIEnv *env, const char *str) {
    return env->NewStringUTF(str);
}

extern "C"
JNIEXPORT jstring
JNICALL
Java_com_example_ndk_ndkdemo_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

本地方法使用Java_前缀,然后跟着以下划线(_)分割的Java全限定类名(com_example_ndk_ndkdemo_MainActivity),最后是和Java方法同名的stringFromJNI字符串。因为我们只有一个方法叫stringFromJNI,VM首先查找短名称(即没有参数签名的名称)就可以找到对应的本地方法实现。

1.2 方法含有重载版本

在没有短名称的前提下,VM会查找具有参数签名的长名称。只有在本地方法被另一个本地方法重载时,程序员才需要使用长名称。如果存在重载版本的函数,而不使用长名称则都会调用到短名称方法上,不能按照我们的预期执行对应的重载版本函数。下面再定义一个名称为stringFromJNI的JNI方法,我们则必须采用含有两个下划线(“__”)后面跟着参数签名的本地方法名才可以。

package com.example.ndk.ndkdemo;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI("hello"));
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();

    public native String stringFromJNI(String str);
}

定义了重载版本的JNI方法,本地方法必须采用长名称才行。

#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring
JNICALL
Java_com_example_ndk_ndkdemo_MainActivity_stringFromJNI__(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

extern "C"
JNIEXPORT jstring
JNICALL
Java_com_example_ndk_ndkdemo_MainActivity_stringFromJNI__Ljava_lang_String_2(
        JNIEnv *env,
        jobject /* this */,
        jstring jstr) {
    std::string hello = "Hello from C++ with param";
    return env->NewStringUTF(hello.c_str());
}

Java中stringFromJNI()方法对应的参数签名是空的,所以本地代码写作Java_com_example_ndk_ndkdemo_MainActivity_stringFromJNI__,仅仅跟了两个下划线(__),而Java中stringFromJNI(String str)方法,对应的本地方法为Java_com_example_ndk_ndkdemo_MainActivity_stringFromJNI__Ljava_lang_String_2。我们知道String类对应的签名为Ljava/lang/String;,将/替换为_,而“;”需要转义,就被转义为了_2,这就是后面跟了个_2的原因。

二、调用RegisterNatives()来注册本地方法

程序员还可以调用JNI函数RegisterNatives()来注册与类关联的本地方法。RegisterNatives()函数对于静态链接的函数特别有用。

2.1 涉及API

2.1.1 RegisterNatives

jint RegisterNatives(JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods);

使用clazz参数指定的类注册本地方法。methods参数指定JNINativeMethod结构的数组,其中包含本地方法的名称,签名和函数指针。 JNINativeMethod结构的名称和签名字段是指向修改后的UTF-8字符串的指针。nMethods参数指定数组中的本地方法数。JNINativeMethod结构定义如下:

typedef struct { 

    char *name; 

    char *signature; 

    void *fnPtr; 

} JNINativeMethod; 

函数指针在名义上必须有以下签名:

ReturnType (*fnPtr)(JNIEnv *env, jobject objectOrClass, ...); 

LINKAGE:

JNIEnv接口函数表中的索引215。

PARAMETERS:

env:JNI接口指针。

clazz:一个Java类对象。

methods:类中的本地方法。

nMethods:类中本地方法的数量。

RETURNS:

成功时返回“0”; 失败时返回负值。

THROWS:

NoSuchMethodError: 如果找不到指定的方法或该方法不是本地方法。

2.1.2 JNI_OnLoad

jint JNI_OnLoad(JavaVM *vm, void *reserved);

当加载本地库时,VM调用JNI_OnLoad(例如,通过System.loadLibrary)。JNI_OnLoad必须返回本地库所需的JNI版本。

为了使用任何新的JNI函数,本地库必须导出返回JNI_VERSION_1_2的JNI_OnLoad函数。如果本地库不导出JNI_OnLoad函数,VM假设该库只需要JNI版本JNI_VERSION_1_1。如果VM不识别JNI_OnLoad返回的版本号,则无法加载本地库。

LINKAGE:

从包含本地方法实现的本地库导出。

SINCE:

JDK/JRE 1.4

为了使用J2SE版本1.2中引入的JNI函数,除了JDK/JRE 1.1中提供的那些函数外,本地库必须导出一个JNI_OnLoad函数,该函数返回JNI_VERSION_1_2。

为了使用J2SE发行版1.4中引入的JNI函数,除了发行版1.2中提供的函数外,本地库还必须导出返回JNI_VERSION_1_4的JNI_OnLoad函数。

如果本地库不导出JNI_OnLoad函数,VM假设该库只需要JNI版本JNI_VERSION_1_1。如果VM不识别JNI_OnLoad返回的版本号,则无法加载本地库。

2.2 实例

继续修改上面的例子,Java层的代码没有保持不变,本地代码修改如下:

#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring
JNICALL
stringFromJNI1(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

extern "C"
JNIEXPORT jstring
JNICALL
stringFromJNI2(
        JNIEnv *env,
        jobject /* this */,
        jstring jstr) {
    std::string hello = "Hello from C++ with param";
    return env->NewStringUTF(hello.c_str());
}

static JNINativeMethod methods[] = {
        {"stringFromJNI", "()Ljava/lang/String;",                   reinterpret_cast<void *>(stringFromJNI1)},
        {"stringFromJNI", "(Ljava/lang/String;)Ljava/lang/String;", reinterpret_cast<void *>(stringFromJNI2)}
};

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

    jclass clazz = env->FindClass("com/example/ndk/ndkdemo/MainActivity");// 申明了JNI方法的Java类

    //注册Native方法
    if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof((methods)[0])) < 0) {
        return JNI_ERR;
    }

    return JNI_VERSION_1_4;
}

在JNI_OnLoad方法内我们把本地方法注册上了,主要调用了RegisterNatives方法,这样可以避免本地方法很长的方法名。reinterpret_cast<void*>(stringFromJNI1) 和reinterpret_cast<void*>(stringFromJNI2) 为本地实现的方法名,这里强制转换成了函数指针。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TYYJ-洪伟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值