用于包装C++对象的最佳 JNI 模式
Best JNI Pattern for wrapping C++ Objects?
我正在开发一个Java API,其中许多Java对象实际上是等效C++对象的包装器。Java 对象创建C++对象,并负责在不再需要它们时释放它们。我想知道为此使用的最佳模式,我可以看到两种可能的选择:
-
使用静态本机方法调用和 final 变量在构造函数中构造 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); }
-
使用实例本机方法调用和非最终变量在构造函数中构造 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
表示)的所有者,因为没有它就无法构造。
相比有什么优点,或者方法(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()
。
也许我错过了其他方法?欢迎提出建议...
您是否尝试过可以生成 c++ 对象的 Java 包装器的 SWIG(http://www.swig.org)?
%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);
}
}
根据基准测试,"通过调用,静态"方法确实是最有效的https://github.com/adamretter/jni-construction-benchmark但是JavaCPP基本上使用"通过调用,调用"(顺便说一句,可以通过缓存jfieldID
来提高效率)。
我选择这样做的原因是生成一个更干净的 Java 接口文件。用户可以决定手动编写它或让该工具从头文件生成它。无论哪种方式,它最终都会像某些头文件的 Java 翻译一样阅读。但是,我们添加到该界面的粗糙越多,它看起来就越不像C++,编写或读取它就越困难。(这是我不喜欢SWIG的众多事情之一。
顺便说一句,不禁止修改 JNI 中的final
变量,所以这可能是人们可能想要这样做的另一个原因。
修改JavaCPP并支持计算效率更高的做事方式,但是我们几乎没有节省任何时间,因为它还没有被证明是一个问题。
- 在C#中处理C++指针而不使用unsafe的最佳方法
- 在C++中,将大的无符号浮点数四舍五入为整数的最佳方法是什么
- 实现无开销push_back的最佳方法是什么
- 在c代码之间共享数据的最佳方式
- 使用std::source_location报告错误的最佳实践
- 派生类销毁的最佳实践是什么
- 通过JNI传递数据数组的最快方法是什么
- 将寄存器设计成可由C和C++访问的外设的最佳实践
- 在两台机器之间进行时间戳的最佳c++chrono函数是什么
- 使用QQuickFramebufferObject时同步数据的最佳方式是什么
- 在C++中向零方向近似的最佳方法
- 使用不同的CRT将新的C++代码与旧的(二进制)组件隔离开来的最佳方法是什么
- 从嵌套在std::映射中的std::列表中删除元素的最佳方式
- 如果条件为TRUE(最佳方式?),则在do while循环中后置增量
- 检测win32服务创建和删除的最佳方法
- 在reactor中存储eventHandlers的最佳方式是什么
- 在C++中样板"冷/never_inline"错误处理技术的最佳方法是什么?
- 在 c++ 中对类中的 c 字符串动态数组进行排序的最佳方法是什么?
- 用于包装C++对象的最佳 JNI 模式
- 从C++访问Java类的最佳方式?(比直接使用JNI要好)