Passing Java object in C ++ through JNI and then back to Java via void *

I have an Android app that uses both React Native and JNI. C ++ (via the JUCE library fork) is used to create one of the views.

React Native requires a new instance of the view to be returned from the overridden createViewInstance(context) method. It seems to be triggered every time the React Native component containing this view is updated.

Here is my (simplified) implementation:

 protected JuceViewHolder createViewInstance(ThemedReactContext themedReactContext) { JuceBridge juceBridge = JuceBridge.getInstance(); juceViewHolder = new JuceViewHolder(themedReactContext); // JNI method: this will trigger JuceBridge.createNewView MainActivity.createJuceWindow(juceViewHolder); return juceViewHolder; 

}

For reference, createJuceWindow is defined as:

 JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, createJuceWindow, jobject, (JNIEnv* env, jclass, jobject view)) { JuceView::getInstance().createJuceWindow(view); } 

which causes:

 void createJuceWindow(jobject view) { viewToAttachTo = GlobalRef(view); // [GlobalRef][1] manages NewGlobalRef myComponent = std::unique_ptr<MyComponent> (new MyComponent); myComponent->setVisible (true); myComponent->setOpaque(true); if (viewToAttachTo.get() == nullptr) DBG ("createJuceWindow: viewToAttachTo null!!!"); myComponent->setBounds(Desktop::getInstance().getDisplays().getMainDisplay().userArea); myComponent->addToDesktop (0, viewToAttachTo.get()); // This passes in the `jobject` held by GlobalRef } 

I detached the JuceViewHolder obtained from the ViewGroup and pass it to the C ++ function via JNI to attach the (Java) ComponentPeerView generated from the (C ++) AndroidComponentPeer constructor

 AndroidComponentPeer (Component& comp, const int windowStyleFlags, void* viewToAttachTo) : ComponentPeer (comp, windowStyleFlags), usingAndroidGraphics (false), fullScreen (false), sizeAllocated (0), scale ((float) Desktop::getInstance().getDisplays().getMainDisplay().scale) { // NB: must not put this in the initialiser list, as it invokes a callback, // which will fail if the peer is only half-constructed. if (viewToAttachTo == nullptr) DBG ("viewToAttachTo null"); view = GlobalRef (android.bridge.callObjectMethod (JuceBridge.createNewView, (jboolean) component.isOpaque(), (jlong) this, (jobject) viewToAttachTo)); if (view.get() == nullptr) DBG ("view null!"); else DBG ("got view."); if (isFocused()) handleFocusGain(); } 

using the createNewView method: (simplified here)

 public final ComponentPeerView createNewView (boolean opaque, long host, ViewGroup viewToAttachTo) { ComponentPeerView v = new ComponentPeerView (viewToAttachTo.getContext(), opaque, host); viewToAttachTo.addView(v); return v; } 

host is a pointer to a C ++ AndroidComponentPeer .

This works initially, however it seems that if I switch to another View or Activity and / or return to the C ++ view, I get a crash similar to the following:

 JNI ERROR (app bug): accessed deleted global reference 0x100566 art/runtime/java_vm_ext.cc:410] JNI DETECTED ERROR IN APPLICATION: use of deleted global reference 0x100566 art/runtime/java_vm_ext.cc:410] from void com.juce.JuceBridge$ComponentPeerView.focusChanged(long, boolean) 

My current assumption is that the error is because the void* pointer becomes obsolete at some point due to the garbage collector moving the underlying object, as described in this article . What I got from the article is that jobject should be used instead of a pointer.

However, the method signature of the used Component::addToDesktop uses void* :

 virtual void addToDesktop (int windowStyleFlags, void* nativeWindowToAttachTo = nullptr); 

(This is the method used in iOS to pass the native UIView to attach Component to)

Is my assumption valid? If so, is it safe to apply the void* pointer to a Java object, save it as a jobject (via NewGlobalRef), and then pass it back to Java?

+5
source share

All Articles