简体   繁体   中英

JNI Keep Object in C++ alive over multiple JNI calls

I have a problem in my Java native audio library but first, here is my current approach:

  1. With a native method I'm opening a 'global' stream, which receives data over a callback function.
  2. The callback function runs until there is no data.
  3. If there is no data, the stream only stops, but does not get closed.
  4. Now I wanted to feed the stream with data again [trying to start stream again(this operation is allowed)], but the stream has already been deleted.

So now I tried to figure out how to prevent deletion of the stream from C++ or Java.

One solution was to create a thread in the stream, which prevents the deletion.

But I don't like this solution...

So I searched how to keep such objects alive and found out, that there are so called "global references" which can be made with the JNI. But I did not understand whether they are only for java objects or for both.

Also I tried out whether another pointer type of C++ could help.

I appreciate any help or ideas, it does not have to be JNI only. C++ standard library methods/functions/classes etc. are also good:) !

System information:

  • Compiler: MinGW64 over MSYS2
  • JDK8u91
  • Of course 64bit operation system (Does not have to be named xD)

With global stream is meant, that the stream is accessible to all JNI methods.

EDIT:

Okay, 'to let the cat out of the back' I'm using RtAudio. Realtime C++ Audio Library

Example:

//THIS IS C++ CODE
RtAudio audio(RtAudio::WASAPI);

int callback(//Buffer stuff etc.){
  //do something
 if(data.isEmpty())return 1;//invokes audio.closeStream() but this does NOT closes the stream!
 else return 0; //Go on with the stream rather wait for the next call
}    


JNIEXPORT void JNICALL openStream(jintArray data){
   //This is a outputstream
   audio.openStream(&outputParams,....., &callback,....);
   audio.startStream();
}

JNIEXPORT void JNICALL fillData(jintArray data){
    //filldata again!
    stream.start(); //Starts the stream but does nothing, because the stream is deleted because of Java 
}

If I would change the openStream method to this, the stream won't be deleted but I look for a better solution...

JNIEXPORT void JNICALL openStream(jintArray data){
   //This is a outputstream
   audio.openStream(&outputParams,....., &callback,....);
   audio.startStream();
 **while(true); //ADD THIS AND THE STREAM WON'T BE DELETED!**  
}

Another solution is to add into the RtAudio API a "keepInstanceAliveThread" which is called after the stopStream() method and deleted after calling startStream() or closeStream(). I would rather prefer another solution but at all, there isn't any yet.

Pre-outcomes:

Thanks to @marcinj:

global object are known to cause many problems, its hard to control their construction/destruction.

EDIT: I found out in the inte.net (also on stackoverflow), that the destructor is called after the return of a JNI method.

Use a long in the Java object to hold a pointer to the C++ object.

A Java long is 64 bits, and every platform Java runs on has either 32- or 64-bit pointers. And every platform Java is supplied for will support this, despite it not being strictly-conforming C or C++ code.

Java:

// class member
private long audio

// native functions
private native long openStream( int[] data );
private native void deleteStream( long audio );
private native void nativeFillData( long audio, int[] data );

public MyClass()
{
    audio = openStream( data );
}

public void fillData( int[] data )
{
    nativeFillData( this.audio, data );
}

// delete the C++ object - you may want to
// control this directly and not rely on
// finalize() getting called
protected void finalize()
{
    deleteStream( audio );
    super.finalize();
}

C++:

JNIEXPORT jlong JNICALL openStream(jintArray data)
{
   RtAudio *audio = new RtAudio(RtAudio::WASAPI);

   audio->openStream(&outputParams,....., &callback,....);
   audio->startStream();

   // C-style cast - JNI interface is C, not C++
   return( ( jlong ) audio );
}

JNIEXPORT void JNICALL deleteStream(jlong jaudio)
{
    RtAudio *audio = static_cast <RtAudio *>( jaudio );
    delete audio;
}

JNIEXPORT void JNICALL nativeFillData(jlong jaudio, jintArray data)
{
    RtAudio *audio = static_cast <RtAudio *>( jaudio );
    audio->start();
    ... 
}

1) JAVA THREAD WAY

We can create a new thread to keep running in a JNI function locked with a monitor or conditional while loop.

Then a separate call would stop the execution of the thread in your function by releasing the monitor or changing the condition in the while loop.

2) JAVA OBJECT REFERENCE WAY

Another option is to create a Global Reference of your object Android JNI and NewGlobalRef . Here a separate call on USB disconnect would do DeleteGlobalRef .

We can also move the life cycle of your C++ object into java by passing it back to the java layer keep some sort of c++ object alive over multiple jni calls . Here a separate call on USB disconnect would remove any reference to C++ object in your java code.

Implementation

Native File (mynative.cpp)

extern "C" JNIEXPORT jobject JNICALL
Java_com_android_nativecpp_MainActivity_createJniNativeReference(JNIEnv* env, jobject obj, jint size) {
    void* buf = malloc(size);
    jobject sharedbytebuffer = env->NewDirectByteBuffer(buf, size);
    return env->NewGlobalRef(sharedbytebuffer);
}

extern "C" JNIEXPORT void JNICALL
Java_com_android_nativecpp_MainActivity_deleteJniNativeReference(JNIEnv* env, jobject obj, jobject sharedbytebuffer) {
    env->DeleteGlobalRef(sharedbytebuffer);
    void* buf = env->GetDirectBufferAddress(sharedbytebuffer);        
    free(buf);
    return;
}

Java file (MainActivity.java)

import java.nio.ByteBuffer;

private ByteBuffer mJniReference;

public native ByteBuffer createJniNativeReference(int size);
public native void deleteJniNativeReference(ByteBuffer mJniReference);

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mJniReference = createJniNativeReference(1000);
}


protected void onDestroy() {
    deleteJniNativeReference(mJniReference);
    super.onDestroy();
}

EXPLAINATION

The reason for either of 1) or 2) is otherwise, the creating frame is then exited from the stack and the JNI local references are deleted.

This will end all the threads in C++ when all std::thread references (which are not detached, ideally) are deleted. In the non-detached case std::thread destructors are called on the main exit, or when a thread object goes out of scope and then terminate() is called. In the detached case the detached threads exit on app close which kills the host process. They can also be garbage collected.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM