2016-01-15 5 views
6

Я работаю над Java API, где многие из объектов Java действительно являются обертками для эквивалентных объектов C++. Объекты Java создают объекты C++ и несут ответственность за освобождение их, когда они больше не требуются. Я задаюсь вопросом о наилучшем шаблон, чтобы использовать для этого, я вижу два возможных варианта:Лучший шаблон JNI для обертывания объектов C++?

  1. Построить C++ объект в конструкторе, используя статический нативный вызов метода и конечную переменную для хранения родной ручки.

    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); 
    } 
    
  2. Построить C++ объекта в конструкторе экземпляр с помощью вызова метода нативного и неконечного переменного для хранения родной ручки.

    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); 
    } 
    

На данный момент я имею в виду, что (1) является лучшим подходом, потому что:

  • а. Это означает, что я могу установить _nativeHandle неизменным (final). Поэтому мне не нужно беспокоиться о параллельном доступе к нему или о неожиданных изменениях (код на самом деле более сложный, чем эти упрощенные примеры).
  • b. Из-за конструктора я формализовал в дизайне, что любой подкласс класса NativeBackedObject является владельцем его соответствующего нативного объекта (представленный _nativeHandle), так как он не может быть построен без него.

Есть ли какие-либо преимущества подхода (2) по сравнению с (1) или любые проблемы с подходом (1)?

Я мог бы также увидеть альтернативный шаблон подойти (1), давайте назовем его подход (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); 
} 

Преимущество (3) по (1), является то, что я могу вернуться к конструктор по умолчанию, который может помочь в создании макетов для тестирования и т. д. Основным недостатком является то, что я больше не могу передавать дополнительные параметры newFoo().

Возможно, есть другие подходы, которые я пропустил? Предложения приветствуются ...

+0

Какой жизненный цикл будут иметь ваши NativeBackedObjects? –

+0

Жизненный цикл будет вручную управляться потребителем API, я реализовал AutoCloseable, поэтому у них есть возможность использовать try-with-resources для управления очисткой. – adamretter

+0

Вы собираетесь делать многопоточность? Я также пытаюсь вычислить ваш '_nativeOwner' AtomicBoolean. Учитывая, что это «конечная» переменная экземпляра «NativeBackedObject», созданная конструктором этого объекта, если у вас нет дополнительного кода, который не отображается, который изменяет то, что вы определяете как право собственности, я не вижу необходимости в этом булевом. В текущем 'NativeBackedObject' есть ** ссылка ** на ваш собственный объект, полученный, как я предполагаю, с помощью' new' в вашем собственном коде. Вам нужно будет передать владение, и я бы избегал этого, если это вообще возможно. Это был бы кошмар O & M. –

ответ

1

Вы пробовали SWIG (http://www.swig.org), которые могут генерировать Java-обертки объектов C++?

%typemap(javabody) SWIGTYPE %{ 
    private long swigCPtr; 
    protected boolean swigCMemOwn; 

    public $javaclassname(long cPtr, boolean cMemoryOwn) { 
     swigCMemOwn = cMemoryOwn; 
     swigCPtr = cPtr; 
    } 

    public static long getCPtr($javaclassname obj) { 
     return (obj == null) ? 0 : obj.swigCPtr; 
    } 
%} 

В документации по SWIG говорит, рассмотрим простой тест:

class Test { 
    string str; 
public: 
    Test() : str("initial") {} 
}; 

выход для него является:

public class Test { 
    private long swigCPtr; 
    protected boolean swigCMemOwn; 

    protected Test(long cPtr, boolean cMemoryOwn) { 
    swigCMemOwn = cMemoryOwn; 
    swigCPtr = cPtr; 
    } 

    protected static long getCPtr(Test obj) { 
    return (obj == null) ? 0 : obj.swigCPtr; 
    } 

    protected void finalize() { 
    delete(); 
    } 

    // Call C++ destructor 
    public synchronized void delete() { 
    if(swigCPtr != 0 && swigCMemOwn) { 
     swigCMemOwn = false; 
      exampleJNI.delete_Test(swigCPtr); 
     } 
     swigCPtr = 0; 
     } 

    // Call C++ constructor 
    public Test() { 
    this(exampleJNI.new_Test(), true); 
    } 

} 
+0

Nope. Знаете ли вы, какой шаблон он следует? – adamretter

+0

Спасибо @ tomasz-jarosik, что очень интересно. Таким образом, похоже, что SWIG делает «By Call» из шаблонов, которые я документировал здесь: https://github.com/adamretter/jni-construction-benchmark – adamretter

+1

Прохладное сравнение! Я думаю, что SWIG использует «By Call, static», поскольку они имеют статическую повсюду. –

1

Подход "вызовом, Static" действительно наиболее эффективным в соответствии с эталоном в https://github.com/adamretter/jni-construction-benchmark , но JavaCPP в основном использует «By Call, Invoke» (который BTW можно сделать немного м руд эффективен путем кэширования jfieldID).

Причина, по которой я решил сделать это, заключается в создании более чистого файла интерфейса Java. Пользователь может решить либо записать его вручную, либо позволить инструменту генерировать его из файлов заголовков.В любом случае, он заканчивается чтением, как перевод Java некоторых файлов заголовков. Однако, чем более крутой мы добавляем к этому интерфейсу, тем меньше он выглядит как C++, и чем сложнее становится его писать, так и читать. (Это одна из многих вещей, которые мне не нравятся в SWIG.)

Кстати, не запрещено изменять переменные из JNI final, так что это может быть другой причиной, по которой можно было бы так сделать.

Конечно, по-прежнему можно изменять JavaCPP и поддерживать более эффективный в вычислительной практике способ делать что-то, но мы с трудом сэкономим время, пока оно еще не доказало свою проблему.

Смежные вопросы