C++ API C 中 Java/JNA 对象引用的包装器
C++ API Wrapper in C for Java/JNA Object references
我正在为我自己的 C++ 库编写一个 C API 包装器。我想在 Java.
中将 C-API 与 JNA 一起使用
对于 C++ 库中的每个 class,我将构造函数包装在 C 函数调用中,返回一个句柄。有了这个句柄,我可以调用 C 函数,这些函数包装了对象的成员函数(将句柄转换为它的 class 并调用它的成员函数)。
这很好用。
为了简化 API 在 Java 中的使用,我为所有 C++ classes 编写了 classes。这些 Java classes 仅包含一个 JNA-Pointer(存储我的对象句柄)和 C++ 调用的 public 成员函数。 Java class 的每个成员函数调用包装 C++ 对象的成员函数的 C 函数。
现在我遇到了一个问题:
当一个C++成员函数returns引用另一个C++对象时,那么我的C-APIreturns指针。但是我如何从这个指针构造我的 Java 对象呢?创建一个新的 Java 对象并将其指针值设置为返回值是否合法。虽然在 Java 中现在有 2 个对象,它们持有相同的指针值。
或者我应该在 Java 中有一个列表,跟踪所有 C++ 对象,这样我就可以从这个列表中获取从 C++ 返回的引用。
垃圾收集器何时从堆中删除 JNA 指针对象?什么时候删除我的 C++ 对象?
(一些代码来指定我的答案)
在我的 C-API 中,我有以下函数来创建一个对象
int C_API_createFoo(void **handle) {
try {
*handle = reinterpret_cast<void*>(new Foo());
return SUCCESS;
} catch (std::exception &err) {
std::cout << err.what() << std::endl;
return ERROR;
}
}
在 Java/JNA 中,我加载我的 C-lib 并使用以下代码调用该函数:
public Pointer createFoo(){
PointerByReference pRef = new PointerByReference();
int eC = MyClib.INSTANCE.C_API_createFoo(pRef);
CHECK(eC);
return pRef.getValue();
}
Foo 在哪里:
public class Foo{
private Pointer handle;
public Foo(){
this.handle = createFoo();
}
public Foo(Pointer handle){
this.handle = handle;
}
}
所以现在 class Foo
存储对象句柄并且可以用作普通的 Java 对象。
但是建议我有一个 C++ class 例如 FooWorker
它有一个类型为 Foo
的成员变量并且有一个 getter 函数用于 Foo
:
int C_API_FooWorker_getFoo(void* FooWorkerHandle, void** fooHandle) {
try {
FooWorker* fW= reinterpret_cast<FooWorker*>(FooWorkerHandle);
Foo *foo = fW.getFoo();
*fooHandle = reinterpret_cast<void*>(foo);
return SUCCESS;
} catch (std::exception &err) {
std::cout << err.what() << std::endl;
return ERROR;
}
}
在 JNA 中映射如下:
public Foo FooWorker_getFoo(Pointer fooWorkerHandle){
PointerByReference pRef = new PointerByReference();
int eC = MyClib.INSTANCE.C_API_FooWorker_getFoo(fooWorkerHandle, pRef);
CHECK(eC);
return new Foo(pRef.getValue());
}
因此,当我使用相同的 fooWorkerHandle
值多次调用函数 FooWorker_getFoo(Pointer fooWorkerHandle)
时,我将获得多个 Java 类型 Foo
的对象,但所有它们的句柄包含相同的值(即相同的 C++ 对象引用)。
问题
我现在的问题是:这是拯救还是会造成任何痛苦?所有这些 Foo 对象(最后是唯一的 C++ 对象,所有指向的句柄)都会被垃圾收集收集吗?
您编写的代码不会导致任何问题,假设您有一个在 C 中释放 Foo
对象的本机方法。
您的描述似乎缺少 Java 端内存和本机内存之间的区别。它们是分开分配和处理的。一些关键点:
- 在
C_API_createFoo()
函数中调用 new Foo()
会为该对象分配本机端内存。您没有显示释放该内存的代码;或对该对象将持续多长时间的任何保证。这不是使用 Java 自动发生的事情,您需要确保本机 API 允许您在完成对象后保留和处置该对象。
- 您可能应该有对应于 JNA 映射的 C 方法,例如
dispose(PointerByReference pRef)
(或类似的),您可以在其中将指针传递给 Foo 以释放该内存。访问指针时,一些 APIs auto-dispose 基于计数器,您可能需要 keep(PointerByReference pRef)
之类的东西来增加本机端的计数器并确保它不会释放该内存太早了。
- 您在 Java 端创建的对象只是普通的 Java 对象,只要不再引用它们,它们就会被垃圾回收。
- 特别是,如果该对象是 Java
Foo
对象中的 PointerByReference
或 Pointer
,它具有本机指针地址的值,但它在 GC 时不会对该本地地址做任何特殊的事情,你必须故意这样做。
- 为了“有意”释放它(与任何本机句柄一样)并避免句柄泄漏,我更喜欢 try-with-resources 或 try-finally 块。
您不需要创建 Java class 来封装 Pointer
。您可能更喜欢扩展 PointerType
class (正是这样做的),它给您一个装饰指针,例如
class FooHandle extends PointerType() {
}
然后您可以将 createFoo()
方法更改为:
public FooHandle createFoo(){
PointerByReference pRef = new PointerByReference();
int eC = MyClib.INSTANCE.C_API_createFoo(pRef);
CHECK(eC);
// call code to make sure the native object is persisted
return new FooHandle(pRef.getValue());
}
正如我提到的,完成后您需要一个 disposeFoo()
方法来释放本机端内存。
对于您仅使用 worker 的第二个示例,您 不是 在本机端创建一个新的 Foo()
对象,您只是调用一个“获取”方法。这意味着您只是在检索指针地址的副本,并且您可以(双关语意)获得任意数量的指针地址,没有任何问题。
我正在为我自己的 C++ 库编写一个 C API 包装器。我想在 Java.
中将 C-API 与 JNA 一起使用对于 C++ 库中的每个 class,我将构造函数包装在 C 函数调用中,返回一个句柄。有了这个句柄,我可以调用 C 函数,这些函数包装了对象的成员函数(将句柄转换为它的 class 并调用它的成员函数)。 这很好用。
为了简化 API 在 Java 中的使用,我为所有 C++ classes 编写了 classes。这些 Java classes 仅包含一个 JNA-Pointer(存储我的对象句柄)和 C++ 调用的 public 成员函数。 Java class 的每个成员函数调用包装 C++ 对象的成员函数的 C 函数。
现在我遇到了一个问题:
当一个C++成员函数returns引用另一个C++对象时,那么我的C-APIreturns指针。但是我如何从这个指针构造我的 Java 对象呢?创建一个新的 Java 对象并将其指针值设置为返回值是否合法。虽然在 Java 中现在有 2 个对象,它们持有相同的指针值。
或者我应该在 Java 中有一个列表,跟踪所有 C++ 对象,这样我就可以从这个列表中获取从 C++ 返回的引用。
垃圾收集器何时从堆中删除 JNA 指针对象?什么时候删除我的 C++ 对象?
(一些代码来指定我的答案)
在我的 C-API 中,我有以下函数来创建一个对象
int C_API_createFoo(void **handle) {
try {
*handle = reinterpret_cast<void*>(new Foo());
return SUCCESS;
} catch (std::exception &err) {
std::cout << err.what() << std::endl;
return ERROR;
}
}
在 Java/JNA 中,我加载我的 C-lib 并使用以下代码调用该函数:
public Pointer createFoo(){
PointerByReference pRef = new PointerByReference();
int eC = MyClib.INSTANCE.C_API_createFoo(pRef);
CHECK(eC);
return pRef.getValue();
}
Foo 在哪里:
public class Foo{
private Pointer handle;
public Foo(){
this.handle = createFoo();
}
public Foo(Pointer handle){
this.handle = handle;
}
}
所以现在 class Foo
存储对象句柄并且可以用作普通的 Java 对象。
但是建议我有一个 C++ class 例如 FooWorker
它有一个类型为 Foo
的成员变量并且有一个 getter 函数用于 Foo
:
int C_API_FooWorker_getFoo(void* FooWorkerHandle, void** fooHandle) {
try {
FooWorker* fW= reinterpret_cast<FooWorker*>(FooWorkerHandle);
Foo *foo = fW.getFoo();
*fooHandle = reinterpret_cast<void*>(foo);
return SUCCESS;
} catch (std::exception &err) {
std::cout << err.what() << std::endl;
return ERROR;
}
}
在 JNA 中映射如下:
public Foo FooWorker_getFoo(Pointer fooWorkerHandle){
PointerByReference pRef = new PointerByReference();
int eC = MyClib.INSTANCE.C_API_FooWorker_getFoo(fooWorkerHandle, pRef);
CHECK(eC);
return new Foo(pRef.getValue());
}
因此,当我使用相同的 fooWorkerHandle
值多次调用函数 FooWorker_getFoo(Pointer fooWorkerHandle)
时,我将获得多个 Java 类型 Foo
的对象,但所有它们的句柄包含相同的值(即相同的 C++ 对象引用)。
问题
我现在的问题是:这是拯救还是会造成任何痛苦?所有这些 Foo 对象(最后是唯一的 C++ 对象,所有指向的句柄)都会被垃圾收集收集吗?
您编写的代码不会导致任何问题,假设您有一个在 C 中释放 Foo
对象的本机方法。
您的描述似乎缺少 Java 端内存和本机内存之间的区别。它们是分开分配和处理的。一些关键点:
- 在
C_API_createFoo()
函数中调用new Foo()
会为该对象分配本机端内存。您没有显示释放该内存的代码;或对该对象将持续多长时间的任何保证。这不是使用 Java 自动发生的事情,您需要确保本机 API 允许您在完成对象后保留和处置该对象。- 您可能应该有对应于 JNA 映射的 C 方法,例如
dispose(PointerByReference pRef)
(或类似的),您可以在其中将指针传递给 Foo 以释放该内存。访问指针时,一些 APIs auto-dispose 基于计数器,您可能需要keep(PointerByReference pRef)
之类的东西来增加本机端的计数器并确保它不会释放该内存太早了。
- 您可能应该有对应于 JNA 映射的 C 方法,例如
- 您在 Java 端创建的对象只是普通的 Java 对象,只要不再引用它们,它们就会被垃圾回收。
- 特别是,如果该对象是 Java
Foo
对象中的PointerByReference
或Pointer
,它具有本机指针地址的值,但它在 GC 时不会对该本地地址做任何特殊的事情,你必须故意这样做。 - 为了“有意”释放它(与任何本机句柄一样)并避免句柄泄漏,我更喜欢 try-with-resources 或 try-finally 块。
- 特别是,如果该对象是 Java
您不需要创建 Java class 来封装 Pointer
。您可能更喜欢扩展 PointerType
class (正是这样做的),它给您一个装饰指针,例如
class FooHandle extends PointerType() {
}
然后您可以将 createFoo()
方法更改为:
public FooHandle createFoo(){
PointerByReference pRef = new PointerByReference();
int eC = MyClib.INSTANCE.C_API_createFoo(pRef);
CHECK(eC);
// call code to make sure the native object is persisted
return new FooHandle(pRef.getValue());
}
正如我提到的,完成后您需要一个 disposeFoo()
方法来释放本机端内存。
对于您仅使用 worker 的第二个示例,您 不是 在本机端创建一个新的 Foo()
对象,您只是调用一个“获取”方法。这意味着您只是在检索指针地址的副本,并且您可以(双关语意)获得任意数量的指针地址,没有任何问题。