本文介绍java中定义的native函数如何对应C/C++文件中的函数。

以最简单的Hello World程序为例,在HelloWorld.java中定义一个native本地方法sayHello(),返回值类型为String。

1
2
3
4
5
6
7
8
9
10
package com.hylee.demo.activity;

public class HelloWorld {

static {
System.loadLibrary("HelloWorld");
}

public native String sayHello();
}

通过javah命令生成对应的头文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/ DO NOT EDIT THIS FILE - it is machine generated /
#include <jni.h>
/ Header for class com_hylee_demo_activity_HelloWorld /

#ifndef _Included_com_hylee_demo_activity_HelloWorld
#define _Included_com_hylee_demo_activity_HelloWorld
#ifdef cplusplus
extern "C" {
#endif
/
Class: com_hylee_demo_activity_HelloWorld
Method: sayHello
Signature: ()Ljava/lang/String;
/
JNIEXPORT jstring JNICALL Java_com_hylee_demo_activity_HelloWorld_sayHello
(JNIEnv
, jobject);

#ifdef
cplusplus
}
#endif
#endif

编写cpp文件实现头文件的函数,简单返回字符串”Hello World!”。

1
2
3
4
5
6
7
8
9
10
11
12
//
// Created by HyLee on 2020/3/22.
//
#include "jni.h"
#include "com_hylee_demo_activity_HelloWorld.h"

JNIEXPORT jstring JNICALL Java_com_hylee_demo_activity_HelloWorld_sayHello
(JNIEnv *env, jobject obj){

return env->NewStringUTF("Hello World!");

}

下面来分析一下代码,首先看cpp文件中的函数Java_com_hylee_demo_activity_HelloWorld_sayHello,在之前NDK原理的文章中也有说到,此函数名的命令方式为Java_包名_类型方法名,其中包名中的.需要改成

该函数原型为:

JNIEXPORT jstring JNICALL Java_com_hylee_demo_activity_HelloWorld_sayHello (JNIEnv *, jobject ;

再来看返回值,JNIEXPORTJNICALL可以认为是固定写法,中间的jstring就是返回值,还记得在哪里看见过jstring吗?事实上就是在上一篇文章NDK引用类型中的java String类对应的JNI类型。由于java文件中定义的本地方法sayHello()返回值为String,所以在此处返回值需要写成对应的JNI类型。

然后再看参数,可以看到cpp文件中的函数存在两个参数,但是我们在java中定义的native本地方法是没有参数的,那么这两个参数是从哪里来的呢?

首先需要说明的是,只要是native本地方法对应的C/C++函数都会有这两个参数,下面来说明这两个参数分别有什么用。

JNIEnv实际上代表java环境,通过JNIEnv*这个指针就可以操作java代码。例如,创建java对象,调用java对象方法或者是获取对象属性等等。

在本例中的env->NewStringUTF("Hello World!");就可以实现创建Java String对象。

再来说jobject,如果java中定义的native本地方法不是static的话,这个obj就代表native本地方法的类实例。反之,obj代表这个native本地方法的类的class对象实例。如果用javah命令自动生成staticnative本地方法头文件的话,第二个参数类型jobject就会变为jclass,实际上这两个类型是一样的,之所以用jclass是因为这样可以通过函数原型看出此函数对应的native本地方法是static的。

因此可以通过jobject来访问成员方法和成员变量等。

好了,两个参数说完后,现在将java中的native本地方法修改一下,让其拥有一个String类型参数,其他不变。

1
public native String sayHello(String hello);

那么用javah命令生成的头文件应该是下面这个样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/ DO NOT EDIT THIS FILE - it is machine generated /
#include <jni.h>
/ Header for class com_hylee_demo_activity_HelloWorld /

#ifndef _Included_com_hylee_demo_activity_HelloWorld
#define _Included_com_hylee_demo_activity_HelloWorld
#ifdef cplusplus
extern "C" {
#endif
/
Class: com_hylee_demo_activity_HelloWorld
Method: sayHello
Signature: ()Ljava/lang/String;
/
JNIEXPORT jstring JNICALL Java_com_hylee_demo_activity_HelloWorld_sayHello
(JNIEnv
, jobject,jstring);

#ifdef
cplusplus
}
#endif
#endif

可以看到相比于之前多了一个jstring类型参数,也就是java中定义的String类型对应的JNI类型。

总结: java中定义的native本地方法会对应C/C++的一个函数,在C/C++的函数中会存在两个固定参数,如果java中的native本地方法没有参数的话,那么该方法对应的C/C++函数就只有两个,如果该方法还有其他参数,那么C/C++函数也会在原来两个参数的基础上再加上对应的参数。值得注意的是,java中的类型不能直接在C/C++中使用,在C/C++中只能使用JNI类型。

最后修改日期:2020年5月14日

留言

撰写回覆或留言