【问题标题】:C++ API Wrapper in C for Java/JNA Object references用于 Java/JNA 对象引用的 C 中的 C++ API 包装器
【发布时间】:2021-02-18 06:31:39
【问题描述】:

我正在为我自己的 C++ 库编写 C API 包装器。我想在 Java 中将 C-API 与 JNA 一起使用。

对于 C++ 库中的每个类,我将构造函数包装在 C 函数调用中,并返回一个句柄。使用这个句柄,我可以调用 C 函数,这些函数包装了对象的成员函数(将句柄转换为它的类并调用它的成员函数)。 这很好用。

为了简化我在 Java 中 API 的使用,我为所有 C++ 类编写了类。这些 Java 类仅包含一个 JNA 指针(存储我的对象句柄)和 C++ 调用的公共成员函数。 Java 类的每个成员函数都调用了 C 函数,该 C 函数包装了 C++ 对象的成员函数。

现在我面临一个问题:
当 C++ 成员函数返回对另一个 C++ 对象的引用时,我的 C-API 将返回指针。但是如何用这个指针构造我的 Java 对象呢?创建一个新的 Java 对象并将其指针值设置为返回值是否合法。尽管在 Java 中现在有 2 个对象,它们持有相同的指针值。
或者我应该在 Java 中有一个 List,跟踪所有 C++ 对象,这样我就可以从这个列表中获取 C++ 返回的引用。

垃圾收集器何时从堆中删除 JNA-Pointer 对象?什么时候删除我的 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;
        }
    }

所以现在Foo 类存储了对象句柄,可以用作普通的Java 对象。

但建议我有一个 C++ 类,例如 FooWorker,它有一个类型为 Foo 的成员变量,并有一个用于 Foo 的 getter 函数:

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

所以当我多次调用函数FooWorker_getFoo(Pointer fooWorkerHandle) 并使用相同的fooWorkerHandle 值时,我将获得多个Foo 类型的Java 对象,但它们的句柄都包含相同的值(即相同的C++对象引用)。​​

问题

我现在的问题是:这是保存还是会引起任何痛苦?并且所有这些 Foo 对象(最后是唯一的 C++ 对象,所有指向的句柄)都会被垃圾收集器收集吗?

【问题讨论】:

  • 通过具体的代码示例,我可以回答更多,但因为这是您的问题太宽泛,并且为高质量的答案提出了太多问题。
  • 感谢您的回答。我编辑了我的问题并添加了一些代码。也许这让我的广泛问题稍微简化了一点。

标签: java c++ c jna


【解决方案1】:

假设您有一个在 C 中释放 Foo 对象的本机方法,那么您编写的代码不会导致任何问题。

您的描述似乎缺少的是 Java 端内存和本机内存之间的区别。它们是单独分配和处理的。一些关键点:

  • C_API_createFoo() 函数内调用new Foo() 会为该对象分配本机端内存。您还没有显示释放该内存的代码;或任何关于该对象将持续多长时间的保证。这不是 Java 自动发生的事情,您需要确保本机 API 允许您在使用完对象后同时保留和处置对象。
    • 您可能应该拥有与 JNA 映射相对应的 C 方法,例如 dispose(PointerByReference pRef)(或类似的),您可以将指针传递给 Foo 以释放该内存。某些 API 在访问指针时会根据计数器自动释放,您可能需要 keep(PointerByReference pRef) 之类的东西来在本机端递增该计数器,并确保它不会过早释放该内存。
  • 您在 Java 端创建的对象只是普通的 Java 对象,只要不再引用它们就会被垃圾回收。
    • 特别是,如果该对象是 Java Foo 对象中的 PointerByReferencePointer,则它具有本机指针地址的值,但当它是GC'd,你必须故意这样做。
    • 为了“有意”释放它(与任何本机句柄一样)并避免句柄泄漏,我更喜欢 try-with-resources 或 try-finally 块。

您不需要创建Java 类来封装Pointer。您可能更喜欢扩展 PointerType 类(正是这样做的),它为您提供了一个装饰指针,例如,

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() 方法来释放本机端内存。

对于仅使用工作器的第二个示例,您在本机端创建新的Foo() 对象,您只是调用“get”方法。这意味着您只是在检索一个指针地址的副本,并且您可以根据需要获取(双关语)尽可能多的地址,没有任何问题。

【讨论】:

  • 好的。这对我帮助很大。我的对象有一个 C-API 删除函数,但我从未调用它,因为我认为我的本机对象正在被 java GC 删除。有没有办法使用 Java Cleaner 来自动化这个过程?但是您的回答也让我有些困惑:您对“保留”功能有何理解?为什么以及在哪里保留我的原生对象?我认为本机端永远不会释放内存,直到我告诉它(使用我的 deleteObj(Pointer p))函数。
  • 啊...感谢您对 PointerType 类的建议。我没有注意到,存在这样的东西。
  • Java 清理器可以工作,但我更喜欢有意释放资源而不是等待 GC。我使用try-with-resources 将处置放在close() 方法中,或者将我的用法包装在try-finally 块中:create(); checkSuccess(); try { doStuff(); } finally { dispose(); }。您可以忽略“保留”。如果您有一个 dispose/destroy 对应物来创建,请使用它。一些本机内存分配方法维护的计数器在使用指针时递减并在 0 处自动释放,您必须通过递增该计数来有意保留它们。
猜你喜欢
  • 2010-12-28
  • 1970-01-01
  • 2011-02-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多