【Java】JNI简单用法

JNI即Java Native Interface,是Java与本地代码(多指C/C++)交互的机制。Java是平台无关的,移植性强,但有时候由于特殊原因,需要与本地接口交互,也就是说在Java中调用C/C++函数,或者在C/C++中调用Java接口,下面介绍其实现方法。

如何在Java中调用native方法?

1、在Java中声明native方法

与普通的Java方法类似,只不过这个方法需要使用native关键字声明,而且只需要native声明即可,不用定义具体的行为。如下面例子中的TestJNI类,testJniAdd是个native方法,在test方法中被调用。

// TestJNI.java
class TestJNI
{
    private native int testJniAdd(int v1, int v2); // using native to declaration

    private void test()
    {
        System.out.println("called native from java: testJniAdd(10, 11) is " + testJniAdd(10, 11));
    }

    public static void main(String args[])
    {
        new TestJNI().test();
    }

    static
    {
        System.loadLibrary("jnitest"); // load native lib with xxx of libxxx.so from the right path
    }
}

2、定义native方法

如果熟悉JNI规则的话,我们可以直接在C/C++中定义native方法,不过还可以借助javah工具帮我们生成.h头文件,毕竟这样还是很方便的。

使用javac生成对应的class文件——

$ javac TestJNI.java

使用javah生成对应的.h文件——

$ javach TestJNI

这样TestJNI.h就生成了,我们来看一下其内容:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class TestJNI */

#ifndef _Included_TestJNI
#define _Included_TestJNI

#ifdef __cplusplus
extern "C" {
#endif

/*
 * Class:     TestJNI
 * Method:    testJniAdd
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_TestJNI_testJniAdd(JNIEnv *, jobject, jint, jint);

#ifdef __cplusplus
}
#endif

#endif // _Included_TestJNI

jni.h是个重要的文件,里面定义了许多有用的数据类型和接口以供JNI使用。

__cplusplus宏定义的处理C/C++代码的使用规则,以C的方式来编译C++中的函数。

重点在于Java_TestJNI_testJniAdd,在testJniAdd方法名前添加了Java标识和TestJNI类名,参数类型和返回值类型的int变成了JNI的jint,而且还多了两个参数JNIEnv和jobject,以及JNICALL和JNIEXPORT的使用,这都是JNI的规则,这些数据类型都可以在jni.h中找到。

Signature签名是JNI的一个特色,用以标记方法的参数类型和返回值类型,圆括号中的是参数类型,括号后面的是返回值类型,上面代码中的I即jint,jni.h中定义了这些标识,如下:

typedef union jvalue {
    jboolean    z;
    jbyte       b;
    jchar       c;
    jshort      s;
    jint        i;
    jlong       j;
    jfloat      f;
    jdouble     d;
    jobject     l;
} jvalue;

然后新建TestJNI.c,定义native方法——

// TestJNI.c
#include "TestJNI.h"
#include <stdio.h>

JNIEXPORT jint JNICALL Java_TestJNI_testJniAdd(JNIEnv *env, jobject obj, jint v1, jint v2)
{   
    printf("%s\n", __func__);

    return v1 + v2;
}

这个native方法只是简单的将两个值相加并返回。

3、编译native方法为共享库

我们可以添加一个Android.mk,然后使用native-build工具来编译生成一个共享库。一个更简单的方法是使用gcc编译:

$ gcc -fpic -shared -o libjnitest.so TestJNI.c

使用gcc编译生成libjnitest.so,有时候可能因为找不到jni.h而编译错误,可使用gcc的-I参数:

$ gcc -fpic -shared -o libjnitest.so TestJNI.c -Ixxx

xxx为本地jni.h的绝对路径。

4、在Java中加载共享库并使用native方法

上面TestJNI.java代码中,使用了System.loadLibrary来加载共享库,参数为jnitest,去掉前缀lib和后缀.so,还可以使用System.load来加载共享库,参数为共享库的绝对路径。

5、运行

$ java TestJNI

下面是log输出,可见Java调用native方法成功。

Java_TestJNI_testJniAdd
called native from java: testJniAdd(10, 11) is 21

如何在native中调用Java方法?

上面介绍了在Java中调用native的一般方法,那么反过来在native中调用Java呢,操作步骤一样,对代码稍作修改即可。

在TestJNI类中添加negate方法给native调用——

class TestJNI
{
    private native int testJniAdd(int v1, int v2); // using native to declaration

    private void test()
    {
        System.out.println("called native from java: testJniAdd(10, 11) is " + testJniAdd(10, 11));
    }

    public int negate(int v)
    {
        return -v;
    }

    public static void main(String args[])
    {
        new TestJNI().test();
    }

    static
    {
        System.loadLibrary("jnitest"); // load native lib with xxx of libxxx.so from the right path
    }
}

negate方法用于取一个数值的相反值。

在TestJNI.c中的Java_TestJNI_testJniAdd这个native实现中调用Java方法——

#include "TestJNI.h"
#include <stdio.h>
#include <stdlib.h>

JNIEXPORT jint JNICALL Java_TestJNI_testJniAdd(JNIEnv *env, jobject obj, jint v1, jint v2)
{
    printf("%s\n", __func__);

    jclass cls = (*env)->FindClass(env, "TestJNI");
    jmethodID mid = (*env)->GetMethodID(env, cls, "negate", "(I)I");
    jint ret = (*env)->CallIntMethod(env, obj, mid, 12);
    printf("called java from native:  negate(12) is %d\n", ret);

    return v1 + v2;
}

在native中调用Java方法,正是利用了native方法的参数env和obj,下面是JNIEnv和jobject的定义:

#ifdef __cplusplus
class _jobject {};
typedef _jobject*       jobject;
#else
typedef void*           jobject;
#endif

 #if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
#else
typedef const struct JNINativeInterface* JNIEnv;
#endif

我们的native方法是在C代码中定义的,所以JNIEnv为const struct JNINativeInterface*,jobject为void*,首先通过FindClass获取jclass,然后通过GetMethodID获取jmethodID,最后根据方法的返回值类型为int而调用CallIntMethod,这样任务就完成了。

运行结果——

Java_TestJNI_testJniAdd
called java from native:  negate(12) is -12
called native from java: testJniAdd(10, 11) is 21

可见,native中调用Java方法成功。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值