Overview
In the last article, I learned about NDK, and this article will continue to learn about JNI. This article is only a note, in case you forget it in the future
JNI data types and type descriptors
In JNI development, the data types of Java cannot be used directly on JNI, and need to be converted. For example, the int in Java is jint in JNI. Let’s learn about the data types.
Basic data type
Java data types | jni data type | description |
---|---|---|
boolean | jboolean | Unsigned char type |
byte | jbyte | Signed 8-bit integer |
char | jchar | Unsigned 16-bit integer |
short | jshort | Signed 16-bit integer |
int | jit | Signed 32-bit integer |
long | jlong | Signed 64-bit integer |
float | jfloat | 32-bit floating point |
Reference data type
java type | JNI type | description |
---|---|---|
java.lang.Object | jobject | Can represent any java object |
java.lang.String | jstring | String object |
java.lang.Class | jclass | class object |
Object[] | jobjectArray | The manifestation of any array of objects in java |
boolean[] | jbooleanArray | Boolean array |
byte[] | jbyteArray | Representation of byte array |
char[] | jcharArray | The representation of char array |
short[] | jshortArray | The representation of the short array |
int[] | jintArray | Representation of int array |
long[] | jlongArray | The representation of the long array |
float[] | jfloatArray | The representation of the float array |
double[] | jdoubleArray | The representation of the double array |
java.lang.Throwable | jthrowable | The manifestation of java throwable |
void | void | No type |
In fact, most of the data types are added lowercase in front of the data types of java j
to indicate the data types of jni
Type descriptor
In the JVM virtual machine, when storing the data type name, it uses the specified descriptor instead of the int, float we use to store
java type | Type descriptor |
---|---|
int | I |
long | J |
byte | B |
short | S |
char | C |
float | F |
double | D |
boolean | WITH |
void | V |
Other reference data types | L+full class name+; |
Array | [ |
method | (Parameter) return value |
Represents a string class
Types of | description |
---|---|
java type | java.lang.String |
jni descriptor | Ljava/lang/String; |
Is L
+ 全类名
, which is .
replaced by /
, and finally added;
Represents an array
Types of | description |
---|---|
java type | String[] |
jni descriptor | [Ljava/; ang/String; |
java type | int[][] |
jni descriptor | [[I |
display method
Types of | description |
---|---|
java type | long f (int n,String s,int arr[]); |
jni descriptor | (ILjava / lang / String; [I) J |
java type | void f (); |
jni type | () V |
JNIEnv 和 JavaVM
JavaVM
javaVM is the representative of the java virtual machine at the jni layer. There is only one JavaVM for a process, and all threads share one JavaVM.
Let's take a look at the JavaVM structure
struct _JavaVM {
const struct JNIInvokeInterface* functions;
#if defined(__cplusplus)
jint DestroyJavaVM()
{
return functions->DestroyJavaVM(this); }
jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
{
return functions->AttachCurrentThread(this, p_env, thr_args); }
jint DetachCurrentThread()
{
return functions->DetachCurrentThread(this); }
jint GetEnv(void** env, jint version)
{
return functions->GetEnv(this, env, version); }
jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)
{
return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }
#endif /*__cplusplus*/
};
The value here is analyzed in the C++ version, you can see that all the methods are JNIInvokeInterface
implemented, let’s look at this structure
struct JNIInvokeInterface {
void* reserved0;
void* reserved1;
void* reserved2;
jint (*DestroyJavaVM)(JavaVM*);
jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);
jint (*DetachCurrentThread)(JavaVM*);
jint (*GetEnv)(JavaVM*, void**, jint);
jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);
};
We see that the JNIInvokeInterface
structure contains 5 functions, you can probably know what they do by looking at the method name, such as: GetEnv
function to get JNIEnv
pointer
When developing from the Java layer to the Native layer, he will automatically create JavaVM
objects, but when developing from the Native layer to the Java layer, we need to actively create JavaVM
objects. We can use the following function to create
jint JNI_CreateJavaVM(JavaVM **p_vm, void **p_env, void *vm_args);
第一个参数:指向JavaVM *的指针,函数调用成功会给JavaVM *赋值
第二个参数:指向JNIEnv *的指针,函数调用成功会给JNIEnv *赋值
第三个参数:是指向JavaVMInitArgs的指针,是初始化虚拟机的参数
JNIEnv
JNIEnv is a thread-related structure, which represents the execution environment of java in this thread
JavaVM: JavaVM is the representative of the java virtual machine at the jni layer. There is only one
JNIEnv globally : each thread has one, and jni may have multiple JNIEnv
native环境创建线程,如果要访问jni,需要调用AttachCurrentThread
方法进行关联,使用DetachCurrentThread
接触关联
在_JavaVM
结构体中,有一个方法getEnv()
可以获取JNIEnv
jint GetEnv(JavaVM *vm, void **env, jint version);
第一个参数:JavaVM虚拟机对象
第二个参数:指向JNIEnv的指针
第三个参数:jni的版本,根据jdk的版本,目前有四种值,分别为 JNI_VERSION_1_1, JNI_VERSION_1_2, JNI_VERSION_1_4, JNI_VERSION_1_6
函数调用成功会给JNIEnv赋值
JNIEnv的作用
我们先看下JNIEnv
结构体
struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions;
#if defined(__cplusplus)
jint GetVersion()
{
return functions->GetVersion(this); }
jclass DefineClass(const char *name, jobject loader, const jbyte* buf,
jsize bufLen)
{
return functions->DefineClass(this, name, loader, buf, bufLen); }
jclass FindClass(const char* name)
{
return functions->FindClass(this, name); }
jmethodID FromReflectedMethod(jobject method)
{
return functions->FromReflectedMethod(this, method); }
jfieldID FromReflectedField(jobject field)
{
return functions->FromReflectedField(this, field); }
...省略很多函数
}
我们可以看到函数的实现最终还是交给了JNINativeInterface
去实现,我们再看下这个结构体
struct JNINativeInterface {
void* reserved0;
void* reserved1;
void* reserved2;
void* reserved3;
jint (*GetVersion)(JNIEnv *);
jclass (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*,
jsize);
jclass (*FindClass)(JNIEnv*, const char*);
jmethodID (*FromReflectedMethod)(JNIEnv*, jobject);
jfieldID (*FromReflectedField)(JNIEnv*, jobject);
/* spec doesn't show jboolean parameter */
jobject (*ToReflectedMethod)(JNIEnv*, jclass, jmethodID, jboolean);
jclass (*GetSuperclass)(JNIEnv*, jclass);
jboolean (*IsAssignableFrom)(JNIEnv*, jclass, jclass);
/* spec doesn't show jboolean parameter */
jobject (*ToReflectedField)(JNIEnv*, jclass, jfieldID, jboolean);
jint (*Throw)(JNIEnv*, jthrowable);
jint (*ThrowNew)(JNIEnv *, jclass, const char *);
jthrowable (*ExceptionOccurred)(JNIEnv*);
void (*ExceptionDescribe)(JNIEnv*);
void (*ExceptionClear)(JNIEnv*);
void (*FatalError)(JNIEnv*, const char*);
...省略很多的方法
}
这个结构体的作用是操作java层的入口,具体有俩个作用
- 调用java函数:使用JNIEnv调用java中的代码
- 操作java对象:java对象传入jni层是jstring,可以使用JNIEnv来操作这个对象
参考:
https://juejin.im/post/5d19bf3f6fb9a07eaa229349
https://www.jianshu.com/p/87ce6f565d37
https://blog.csdn.net/afei__/article/details/80986203