Sto lavorando su un'API Java in cui molti degli oggetti Java sono realmente wrapper per oggetti C++ equivalenti. Gli oggetti Java creano gli oggetti C++ e sono responsabili della loro liberazione quando non sono più necessari. Mi chiedo circa il miglior modello da utilizzare per questo, posso vedere due opzioni possibili:Miglior pattern JNI per il wrapping di oggetti C++?
Construct oggetto C++ nel costruttore con una chiamata di metodo nativo statica e variabile finale per tenere la maniglia nativo.
public abstract class NativeBackedObject1 implements java.lang.AutoCloseable { protected final long _nativeHandle; protected final AtomicBoolean _nativeOwner; protected NativeBackedObject1(final long nativeHandle) { this._nativeHandle = nativeHandle; this._nativeOwner = new AtomicBoolean(true); } @Override public close() { if(_nativeOwner.copareAndSet(true, false)) { disposeInternal(); } } protected abstract void disposeInternal(); } public SomeFoo1 extends NativeBackendObject1 { public SomeFoo1() { super(newFoo()); } @Override protected final void disposeInternal() { //TODO: any local object specific cleanup disposeInternal(_nativeHandle); } private native static long newFoo(); private native disposeInternal(final long nativeHandle); }
Costruisce oggetto C++ nel costruttore utilizzando un metodo di chiamata esempio nativo e una variabile non finale per tenere la maniglia nativo.
public abstract class NativeBackedObject2 implements java.lang.AutoCloseable { protected long _nativeHandle; protected boolean _nativeOwner; protected NativeBackedObject2() { this._nativeHandle = 0; this._nativeOwner = true; } @Override public void close() { synchronized(this) { if(_nativeOwner && _nativeHandle != 0) { disposeInternal(); _nativeHandle = 0; _nativeOwner = false; } } } protected abstract void disposeInternal(); } public SomeFoo2 extends NativeBackendObject2 { public SomeFoo2() { super(); _nativeHandle = newFoo(); } @Override protected final void disposeInternal() { //TODO: any local object specific cleanup disposeInternal(_nativeHandle); } private native long newFoo(); private native disposeInternal(final long nativeHandle); }
Al momento mi sto pensando che (1) è l'approccio migliore, perché:
- a. Significa che posso impostare
_nativeHandle
come immutabile (final
). Quindi non ho bisogno di preoccuparmi dell'accesso simultaneo ad esso o di cambiamenti inaspettati (il codice è in realtà più complesso di questi esempi semplicistici). - b. A causa del costruttore, ho formalizzato nel progetto che qualsiasi sottoclasse di
NativeBackedObject
è il proprietario del rispettivo oggetto nativo (rappresentato da_nativeHandle
), poiché non può essere costruito senza di esso.
Esistono vantaggi dell'approccio (2) su (1) o di eventuali problemi di approccio (1)?
ho potuto anche vedere un modello alternativo per avvicinarsi (1), chiamiamola approccio (3):
public abstract class NativeBackedObject3 implements java.lang.AutoCloseable {
protected final long _nativeHandle;
protected final AtomicBoolean _nativeOwner;
protected NativeBackedObject3() {
this._nativeHandle = newInternal();
this._nativeOwner = new AtomicBoolean(true);
}
@Override
public close() {
if(_nativeOwner.copareAndSet(true, false)) {
disposeInternal();
}
}
protected abstract long newInternal();
protected abstract void disposeInternal();
}
public SomeFoo3 extends NativeBackendObject3 {
public SomeFoo3() {
super();
}
@Override
protected final void disposeInternal() {
//TODO: any local object specific cleanup
disposeInternal(_nativeHandle);
}
@Override
protected long newInternal() {
return newFoo();
};
private native long newFoo();
private native disposeInternal(final long nativeHandle);
}
Il vantaggio di (3) sopra (1), è che posso tornare a un costruttore predefinito, che potrebbe aiutare a creare dei mock per i test, ecc. Il principale svantaggio è che non posso più passare parametri addizionali a newFoo()
.
Forse ci sono altri approcci che mi sono perso? Suggerimenti benvenuto ...
Che tipo di ciclo di vita avrà il tuo NativeBackedObjects? –
Il ciclo di vita sarà gestito manualmente dal consumatore dell'API, ho implementato AutoCloseable in modo che abbiano la possibilità di utilizzare try-with-resources per gestire la pulizia. – adamretter
Stai per fare un multithreading? Sto anche cercando di capire il tuo "_nativeOwner" AtomicBoolean. Poiché si tratta di una variabile di istanza 'finale' di' NativeBackedObject' creata dal costruttore di quell'oggetto, a meno che non si disponga di codice aggiuntivo non mostrato che modifica ciò che si definisce come proprietà, non vedo alcuna necessità per tale booleano. L'attuale 'NativeBackedObject' ha ** il ** riferimento al tuo oggetto nativo, ottenuto assumendo tramite' new' nel tuo codice nativo. Dovresti passare la proprietà in giro, e lo eviterei se possibile. Sarebbe un incubo O & M. –