当前位置:网站首页>On the world of NDK (2)
On the world of NDK (2)
2022-07-06 07:06:00 【Yishui south wind】
Last one On ndk The world of ( One ) It mainly introduces ndk Background and java and C++ How to interact , If you haven't read the last one , Then you'd better have a look first , Because this article will continue what was left unfinished in the previous one ndk topic of conversation , Let's be right ndk Our world is a little wider .
Handle Java object
In the last article, it was said that Java Of String Object is passed as a method parameter to C++ Layer , Then how to deal with ordinary custom objects ?
Native Revision in China Java The object of the transmission
So let's create one Java class Person:
public class Person {
private String name;
private int age;
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 +
'}';
}
}
Java Add to layer Native Method :
public native Person setInfoForPerson(Person person);
C++ Layer Native Method :
extern "C"
JNIEXPORT jobject JNICALL
Java_com_example_ndkdemo_MainActivity_setInfoForPerson(JNIEnv *env, jobject thiz, jobject person) {
// Find the object Java class
jclass personClass = env->FindClass("com/example/ndkdemo/Person");
// Corresponding Java attribute
jfieldID name = env->GetFieldID(personClass, "name", "Ljava/lang/String;");
jfieldID age = env->GetFieldID(personClass, "age", "I");
// Attribute assignment ,person For the incoming Java object
env->SetObjectField(person, name, env->NewStringUTF("wangtao"));
env->SetIntField(person, age, 20);
return person;
}
Right Java Modifying the properties of an object is still a reflection operation , First get the attribute id, And then call JNIEnv Of set( Type name )Field I can modify it .
As I said before 3 Step curve :
- obtain Java The object corresponds to class The object is Native Mapping of layers jclass object .
- adopt jclass Object gets properties id.
- Passing attribute id To modify the value .
Under test , stay Java in MainActivity Of onCreate Method to add :
Person person = setInfoForPerson(new Person());
Log.d("MainActivity","setInfoForPerson:" + person.toString());
Under operation :
D/MainActivity: setInfoForPerson:Person{name=‘wangtao’, age=20}
Assignment successful , Very stable !
Native To create a Java Object and return it to Java layer
Java Add Native Method :
public native Person getNewPerson();
Native To realize :
extern "C"
JNIEXPORT jobject JNICALL
Java_com_example_ndkdemo_MainActivity_getNewPerson(JNIEnv *env, jobject thiz) {
jclass personClass = env->FindClass("com/example/ndkdemo/Person");
// Get the constructor of the class , Remember here is to call the parameterless constructor
jmethodID id = env->GetMethodID(personClass, "<init>", "()V");
// Create a new object
jobject person_ = env->NewObject(personClass, id);
jfieldID name = env->GetFieldID(personClass, "name", "Ljava/lang/String;");
jfieldID age = env->GetFieldID(personClass, "age", "I");
env->SetObjectField(person_, name, env->NewStringUTF("wangtao"));
env->SetIntField(person_, age, 20);
return person_;
}
And the above routine , The only difference is to create new objects , You have to get Person Construction method of id, Then through the construction method id establish Person object , Then there is the trilogy modification mentioned above Person Object property value .
Test it ? Something's wrong , It must be stable ~~
quote
stay jni in , Whether from Java Pass the layer to Native Layer or Native Created by layer Java object , Nature is Native The layer holds points jvm A reference to an object in the heap , because jvm Is to use gc root The reference counting method of determines whether the object should be recycled , So when Native Layers hold points Java After the reference of the object , Manage the life cycle of an object , Preventing memory leaks is particularly important .
Partial quote
stay jni in , Whether from Java Layer is passed as a method parameter to Native Layer or Native Created in the layer method Java object (jobject etc. jni Objects also belong to ), By default, it is pointed to by a local reference , That is, the reference will be automatically released when the method returns , You can also manually release before the method returns . and Java equally , The purpose of releasing references is to tell jvm This object is no longer needed , Give Way jvm Decide whether to recycle .
Take a chestnut :
extern "C"
JNIEXPORT void JNICALL
Java_com_example_ndkdemo_MainActivity_testLocalReference(JNIEnv *env, jobject thiz) {
jclass personClass1 = env->FindClass("com/example/ndkdemo/Person");
// Get the constructor of the class , Remember here is to call the parameterless constructor
jmethodID id = env->GetMethodID(personClass1, "<init>", "()V");
// Create a new object
jobject person = env->NewObject(personClass1, id);
// It can also be explicitly specified as a local reference
// person = env->NewLocalRef(person);
env->DeleteLocalRef(person);
// Not used later person object
}
here person Is in Native Created in the jni object , The essence is to point to jvm Heap person A local reference to an object , Once the reference is released , You can't use it later person This quote .( If you are interested, you can try , Guarantee to flash back ~)
Global references
Local reference in Native Method will be released automatically after execution , Suppose we need a method that can be used across methods , Or objects used across threads ? Of course, there are parts , There will be global references , If according to Java Customary writing , It will be written like this :
// Define a “ Global references ”
jclass personClass;
extern "C"
JNIEXPORT void JNICALL
Java_com_example_ndkdemo_MainActivity_testReference(JNIEnv *env, jobject thiz) {
// Although the second entry personClass Not for null, But the object pointed to has been released , Become a dangling pointer
if (personClass == nullptr){
personClass = env->FindClass("com/example/ndkdemo/Person");
}
LOGD("testReference GetMethodID");
// Get the constructor of the class , Remember here is to call the parameterless constructor
jmethodID id = env->GetMethodID(personClass, "<init>", "()V");
}
In this way, only the first call testReference Method is created personClass object , Later call testReference Method is no longer created , Under operation , ok , Successful flashback .
Why? ? Because in Java Write it like this ,jvm I won't see it personClass The reference always points to the corresponding object in the heap ,jvm It won't be recycled . But here we use a common reference to refer to this object , I'm sorry , Once the method is done ,jvm Just recycle the object , that personClass This reference is executed the second time testReference Method is really not for null, But it has become a dangling pointer .
So using global references here is the right way :
// Define a reference
jobject personGlobalReference;
extern "C"
JNIEXPORT jobject JNICALL
Java_com_example_ndkdemo_MainActivity_testGlobalReference(JNIEnv *env, jobject thiz) {
jclass personClass1 = nullptr;
if (personGlobalReference == nullptr){
personClass1 = env->FindClass("com/example/ndkdemo/Person");
// Get the constructor of the class , Remember here is to call the parameterless constructor
jmethodID id = env->GetMethodID(personClass1, "<init>", "()V");
// Create a new object
jobject person = env->NewObject(personClass1, id);
// Key statement , take person Wrap as a global reference and assign to personGlobalReference
personGlobalReference = env->NewGlobalRef(person);
}
jfieldID name = env->GetFieldID(personClass1, "name", "Ljava/lang/String;");
jfieldID age = env->GetFieldID(personClass1, "age", "I");
env->SetObjectField(personGlobalReference, name, env->NewStringUTF("wangtao"));
env->SetIntField(personGlobalReference, age, 20);
return personGlobalReference;
}
Global references are equivalent to telling jvm, I need to use this object , You must not release , So before manual release ,jvm The object will not be released , So remember to release the global reference manually when you don't need to use the object :
env->DeleteGlobalRef(personGlobalReference);
Again , Once released , You cannot use this reference again .
Weak reference
We know ,Java To prevent memory leaks , Weak references are often used in situations such as internal classes or asynchrony , that Native Layer also has such requirements , You also want to have the effect of global reference , I don't want to organize jvm Recycling of objects . and Java equally , Similar to global references , Just one more step to judge whether the object pointed to by the weak reference has been recycled :
// Define a reference
jobject personWeakReference;
extern "C"
JNIEXPORT jobject JNICALL
Java_com_example_ndkdemo_MainActivity_testWeakReference(JNIEnv *env, jobject thiz) {
jclass personClass1 = nullptr;
// Determine whether the object pointed to by the weak reference has been recycled
jboolean isEqual = env->IsSameObject(personWeakReference, NULL);
if (personWeakReference == nullptr || isEqual){
personClass1 = env->FindClass("com/example/ndkdemo/Person");
// Get the constructor of the class , Remember here is to call the parameterless constructor
jmethodID id = env->GetMethodID(personClass1, "<init>", "()V");
// Create a new object
jobject person = env->NewObject(personClass1, id);
// // Key statement , take person Package as weak reference and assign to personWeakReference
personWeakReference = env->NewWeakGlobalRef(person);
env->DeleteLocalRef(person);
}
return personWeakReference;
}
here personWeakReference Refer to the Java Layer of person object , But it doesn't stop jvm Recycling person object , So the next time you use it , Need to hear through env->IsSameObject Method to determine whether the object referenced by the weak reference has been jvm Recycling .
Handling arrays
Native Layers can also handle Java The array passed in , Here is divided into 2 In this case , One is Native Layer creates an array and returns it to Java, The other is to modify Java The array passed in :
Create an array and return it to Java
Java establish Native Method :
public native int[] newIntArray();
Native The implementation of layer is as follows :
extern "C"
JNIEXPORT jintArray JNICALL
Java_com_example_ndkdemo_MainActivity_newIntArray(JNIEnv *env, jobject thiz) {
jint nativeArr[3] = {
1,2,3};
jintArray javaArray = env->NewIntArray(3);
// take C The modification of the array is synchronized to Java Array
env->SetIntArrayRegion(javaArray,0,3,nativeArr);
return javaArray;
}
stay Native Layer ,jintArray Namely Java in int[]“ Parallel world ” Mapping , But in the Native in jintArray The use of is really awkward , It can't be done in one step , First create jint Array , And then create jintArray object , The final will be jint Array pass SetIntArrayRegion Method to jintArray , And then put jintArray Return to Java.
modify Java The array passed in
It's divided into 2 Ways of planting :
Modify by manipulating the array copy Java Array
extern "C"
JNIEXPORT jintArray JNICALL
Java_com_example_ndkdemo_MainActivity_changeArrayByCopy(JNIEnv *env, jobject thiz, jintArray javaArray) {
// get Java The length of the array passed in
jsize length = env->GetArrayLength(javaArray);
// Define a C Array
// take Java The array area is copied to jint Array
jint nativeArr[length];
env->GetIntArrayRegion(javaArray,0,length,nativeArr);
// Modify the value of an element
for(int i=0;i<length;i++){
nativeArr[i]=nativeArr[i]*2;
}
// from C Array orientation Java Array commit changes .0 It is the beginning of synchronous data index,length Is the number of synchronized data
env->SetIntArrayRegion(javaArray,0,length,nativeArr);
return javaArray;
}
The core here is to pass GetIntArrayRegion The copy came in Java Array to jint Array nativeArr, modify nativeArr Then pass SetIntArrayRegion Methods will nativeArr Synchronize to Java Array .
Modify through array pointer Java Array
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_ndkdemo_MainActivity_getArraySumByPointer(JNIEnv *env, jobject thiz,jintArray javaArray) {
jint* nativeArray;
jboolean isCopy;
jint sum = 0;
jsize length = env->GetArrayLength(javaArray);
// use GetArrayElements Function to get a direct pointer to an array element ,isCopy Indicates whether the array pointed to by the pointer is from Java A copy of the array
nativeArray = env->GetIntArrayElements(javaArray, &isCopy);
for(int i=0;i<length;i++){
sum += nativeArray[i];
}
// Release nativeArray The pointer , And synchronize the data to Java Array if GetIntArrayElements Have occurred Copy
env->ReleaseIntArrayElements(javaArray, nativeArray, 0);
return sum;
}
This way is through GetIntArrayElements Get the pointer to the array , Notice the second parameter here , Is a return value parameter , Through this parameter, the system tells us whether the array pointed to by the returned pointer is the original Java Array or original Java Copy of array .
Here, after the operation of the array , Remember to call ReleaseIntArrayElements Release pointer nativeArray, This method has another key point , Is in the GetIntArrayElements Back to isCopy yes true Under the circumstances , It can also synchronize the processing of replicas to Java Array .
The third parameter indicates the specific processing method :
mode actions
0 copy back the content and free the elems buffer( Copy the contents of the copy to the original array and release the array pointer )
JNI_COMMIT copy back the content but do not free the elems buffer( Copy the contents of the copy to the original array , But do not release the array pointer )
JNI_ABORT free the buffer without copying back the possible changes( Release array pointer , But do not copy the contents of the copy to the original array )
Method dynamic registration
As I said before Native All are Java_ + class A full name + Method b The name formed by splicing goes with Java Corresponding Native Method to do mapping , The shortcomings here are also obvious , Once the dynamic link library is loaded Java Class change name , that Native All the methods of have to be modified , So in addition to this one , There is another way to deal with changes more handy registration : Dynamic registration .
What is dynamic registration , Is to carry out Native Manually register these methods at some point in the code , here jni The moment of choice is when the dynamic link library is loaded ( namely Java Call in System.loadLibrary When ).
The steps here are divided into 3 Step :
- Create good Java Of Native Method and corresponding C++ Layer method , Notice now C++ The name of the method can be used freely .
- stay C++ Create a JNINativeMethod Array , To specify C++ Methods and Java The mapping relationship of methods .
- stay C++ Of JNI_OnLoad Method to complete the pair Native Method registration .
The following details :
step 1:
Java Layer creation 2 individual Native Method :
native void dynamicNative();
native String dynamicNative(int i);
Here, in order to better distinguish different methods , So it is specially used 2 Multiple overloaded methods .
C++ Layers are also created 2 A way , Random names :
extern "C"
JNIEXPORT void JNICALL
dynamicNative1(JNIEnv *env, jobject thiz) {
LOGD("dynamicNative1 Dynamic registration ");
}
extern "C"
JNIEXPORT jstring JNICALL
dynamicNative2(JNIEnv *env, jobject thiz, jint i) {
LOGD("dynamicNative2 Dynamic registration ");
return env->NewStringUTF(" I register dynamically dynamicNative2 Method ");
}
step 2:
Create a JNINativeMethod Array , To specify C++ Methods and Java The mapping relationship of methods :
// Array of methods that need to be dynamically registered
static const JNINativeMethod mMethods[] = {
{
"dynamicNative","()V", (void *)dynamicNative1},
{
"dynamicNative", "(I)Ljava/lang/String;", (jstring *)dynamicNative2}
};
JNINativeMethod It's a structure :
typedef struct {
//Java Of Native Method name
const char* name;
//Java Of Native Method Descriptor
const char* signature;
//C++ Function pointer in
void* fnPtr;
} JNINativeMethod;
step 3:
First of all, let's talk about it JNI_OnLoad What is a function ?
JNI_OnLoad Function is a function that calls back when the dynamic link library is loaded , This function returns what the current dynamic link library needs jni Version to jvm, Generally, you can do some initialization , For example, method registration .
JavaVM* _vm = nullptr;
jint JNI_OnLoad(JavaVM* vm, void* reserved){
//JavaVM* vm Is an object representing a virtual machine
_vm = vm;
JNIEnv* env = NULL;
// Get the... Of the current thread JniEnv
int r = vm->GetEnv((void**) &env, JNI_VERSION_1_4);
if( r != JNI_OK){
return -1;
}
jclass mainActivityCls = env->FindClass( mClassName);
// register If it is less than 0 Registration failed
r = env->RegisterNatives(mainActivityCls,mMethods,2);
if(r != JNI_OK )
{
return -1;
}
return JNI_VERSION_1_4;
}
It was said in the last article that ,JNIEnv Objects are unique to threads , Here we can use the parameter JavaVM* vm, To get the corresponding JNIEnv The pointer , Re pass JNIEnv Get the loaded Java class , Here is MainActivity Of jclass object , And then through env->RegisterNatives(mainActivityCls,mMethods,2); Map methods to arrays and loaded Java Class for final dynamic registration .
jni Create thread
Java Layer can create new threads , Of course C++ Layer is certainly ok , Of course , When C++ The created thread needs and Java When the layer interacts , Need some special treatment .
The example here is Java A call to a Native Method in C++ Create a thread , Then call back a Java Method .
Java Create a Native And an ordinary Java Method :
// Trigger C++ How to create a thread
native void createNativeThread();
//C++ Created thread callback Java Method
private void callBackForNewThread(){
Log.d("MainActivity","callBackForNewThread Thread:" + Thread.currentThread());
}
C++ Created in createNativeThread Corresponding method :
jobject _instance = nullptr;
extern "C"
JNIEXPORT void JNICALL
Java_com_example_ndkdemo_MainActivity_createNativeThread(JNIEnv *env, jobject thiz) {
pthread_t pid;
// Use global references to MainActivity Save the instance , For child threads
_instance = env->NewGlobalRef(thiz);
// Create a new thread to run task Method
pthread_create(&pid,0,task,0);
}
The specific function of the new thread :
void *task(void *args) {
JNIEnv *env;
// Attach the local current thread to jvm, And get jnienv
// Return on success 0
_vm->AttachCurrentThread(&env, 0);
// obtain jclass object
jclass activityClass = env->GetObjectClass(_instance);
// obtain Java callBackForNewThread Methodical jmethodID
jmethodID callBackMethod = env->GetMethodID(activityClass, "callBackForNewThread", "()V");
std::string hello = "I am CallBack";
jstring s = env->NewStringUTF(hello.c_str());
// call Java Method
env->CallVoidMethod(_instance, callBackMethod);
// Separate
_vm->DetachCurrentThread();
return 0;
}
The key is to call
_vm->AttachCurrentThread(&env, 0);
Get the corresponding thread JNIEnv object , Get JNIEnv object , It's equivalent to getting access to Java The key to the parallel world , Then call in the previous reflection mode Java Layer of callBackForNewThread Method .
Finally remember to call :
// Detaching threads
_vm->DetachCurrentThread();
Under test , stay MainActivity Of onCreate Method to add :
createNativeThread();
Log.d("MainActivity","main Thread:" + Thread.currentThread());
Under operation :
D/MainActivity: main Thread:Thread[main,5,main]
D/MainActivity: callBackForNewThread Thread:Thread[Thread-3,10,main]
Previous log Is print out run Activity The main thread , After a log Is to run callBackForNewThread Method thread , namely C++ Thread created in .
This is a good habit , Because it can release all the locks held by the thread , To avoid blocking other locks and even causing deadlocks .
summary
This paper introduces ndk in C++ How does the layer deal with Java object 、 Dynamic registration of arrays and methods 、C++ Create a new thread and neutralize it Java The content of the interaction . The next article finally starts the real theme , Start to really enter the world of audio and video ~
If you think this article helps , Don't forget to like it ~
边栏推荐
- 作者已死?AI正用艺术征服人类
- [advanced software testing step 1] basic knowledge of automated testing
- Top test sharing: if you want to change careers, you must consider these issues clearly!
- PCL实现选框裁剪点云
- 微信公众号无限回调授权系统源码 全网首发
- C language_ Double create, pre insert, post insert, traverse, delete
- 19. Actual memory management of segment page combination
- leetcode35. 搜索插入位置(简单,找插入位置,不同写法)
- A brief introduction of reverseme in misc in the world of attack and defense
- OpenGL ES 学习初识(1)
猜你喜欢
Leetcode59. spiral matrix II (medium)
微信公众号无限回调授权系统源码 全网首发
Configure raspberry pie access network
CDN acceleration and cracking anti-theft chain function
Hydra common commands
Bitcoinwin (BCW): the lending platform Celsius conceals losses of 35000 eth or insolvency
Kubernetes cluster builds ZABBIX monitoring platform
19.段页结合的实际内存管理
19. Actual memory management of segment page combination
Introduction to ros2 installation and basic knowledge
随机推荐
Cookie Technology & session Technology & ServletContext object
CDN acceleration and cracking anti-theft chain function
[server data recovery] case of offline data recovery of two hard disks of IBM server RAID5
BIO模型实现多人聊天
Leetcode 78: subset
攻防世界 MISC中reverseMe简述
PCL realizes frame selection and clipping point cloud
中青看点阅读新闻
Windows Server 2016 standard installing Oracle
【每日一题】729. 我的日程安排表 I
18.多级页表与快表
作者已死?AI正用艺术征服人类
基于PyTorch和Fast RCNN快速实现目标识别
Compile, connect -- notes-2
LeetCode 78:子集
UWA pipeline version 2.2.1 update instructions
Leetcode35. search the insertion position (simple, find the insertion position, different writing methods)
librosa音频处理教程
[daily question] 729 My schedule I
巴比特 | 元宇宙每日必读:中国互联网企业涌入元宇宙的群像:“只有各种求生欲,没有前瞻创新的雄心”...