与 JNI 相比,JNA 似乎更容易用于调用本机代码。在什么情况下你会使用 JNI 而不是 JNA?
10 回答
- JNA 不支持 c++ 类的映射,因此如果您使用 c++ 库,则需要一个 jni 包装器
- 如果你需要大量的内存复制。例如,您调用一个返回大字节缓冲区的方法,您更改其中的某些内容,然后您需要调用另一个使用此字节缓冲区的方法。这将要求您将此缓冲区从 c 复制到 java,然后将其从 java 复制回 c。在这种情况下,jni 将在性能上获胜,因为您可以在 c 中保留和修改此缓冲区,而无需复制。
这些都是我遇到的问题。也许还有更多。但总的来说,jna 和 jni 的性能差别不大,所以只要你可以使用 JNA,就使用它。
编辑
这个答案似乎很受欢迎。所以这里有一些补充:
- 如果您需要映射 C++ 或 COM,JNAerator 的创建者 Oliver Chafic 提供了一个名为BridJ的库。它仍然是一个年轻的图书馆,但它有许多有趣的特点:
- 动态 C/C++/COM 互操作:调用 C++ 方法,创建 C++ 对象(以及从 Java 继承 C++ 类!)
- 使用泛型的简单类型映射(包括更好的指针模型)
- 完整的 JNAerator 支持
- 适用于 Windows、Linux、MacOS X、Solaris、Android
- 至于内存复制,我相信JNA支持直接ByteBuffers,所以可以避免内存复制。
所以,我仍然认为,只要有可能,最好使用 JNA 或 BridJ,如果性能至关重要,则恢复为 jni,因为如果您需要频繁调用本机函数,性能损失是显而易见的。
很难回答这样一个笼统的问题。我想最明显的区别是,使用 JNI,类型转换是在 Java/native 边界的 native 侧实现的,而使用 JNA,类型转换是在 Java 中实现的。如果您已经对使用 C 编程感到很自在并且必须自己实现一些本机代码,我会假设 JNI 看起来不会太复杂。如果您是 Java 程序员并且只需要调用第三方本地库,那么使用 JNA 可能是避免 JNI 可能不那么明显的问题的最简单途径。
尽管我从未对任何差异进行基准测试,但由于设计原因,我至少会假设在某些情况下使用 JNA 进行类型转换会比使用 JNI 执行得更差。例如,在传递数组时,JNA 将在每个函数调用开始时将它们从 Java 转换为原生,并在函数调用结束时转换回来。使用 JNI,您可以控制自己何时生成数组的本机“视图”,可能只创建数组的一部分的视图,在多个函数调用中保持视图,最后释放视图并决定是否需要保留更改(可能需要将数据复制回来)或丢弃更改(无需复制)。我知道您可以使用 Memory 类在 JNA 的函数调用中使用本机数组,但这也需要内存复制,这对于 JNI 可能是不必要的。差异可能无关紧要,但如果您最初的目标是通过在本机代码中实现部分应用程序来提高应用程序性能,那么使用性能较差的桥接技术似乎不是最明显的选择。
- 几年前,您在 JNA 出现之前编写代码,或者针对 1.4 之前的 JRE。
- 您正在使用的代码不在 DLL\SO 中。
- 您正在处理与 LGPL 不兼容的代码。
这只是我能想到的,尽管我不是任何一个的重度用户。如果您想要一个比他们提供的接口更好的接口,您似乎也可以避免使用 JNA,但您可以在 java 中编写代码。
顺便说一句,在我们的一个项目中,我们保留了一个非常小的 JNI 足迹。我们使用协议缓冲区来表示我们的域对象,因此只有一个本地函数来桥接 Java 和 C(当然,那个 C 函数会调用一堆其他函数)。
这不是一个直接的答案,而且我没有使用 JNA 的经验,但是当我查看使用 JNA 的项目并看到 SVNKit、IntelliJ IDEA、NetBeans IDE 等名称时,我倾向于相信它是一个相当不错的库。
实际上,我绝对认为我会在必要时使用 JNA 而不是 JNI,因为它看起来确实比 JNI 更简单(开发过程很无聊)。太糟糕了,JNA 没有在这个时候发布。
我实际上用 JNI 和 JNA 做了一些简单的基准测试。
正如其他人已经指出的那样,JNA 是为了方便。使用 JNA 时无需编译或编写本机代码。JNA 的本机库加载器也是我见过的最好/最容易使用的库之一。可悲的是,您似乎不能将它用于 JNI。(这就是为什么我为 System.loadLibrary() 编写了一个替代方案,它使用 JNA 的路径约定并支持从类路径(即 jars)无缝加载。)
然而,JNA 的性能可能比 JNI 差得多。我做了一个非常简单的测试,它调用了一个简单的本机整数增量函数“return arg + 1;”。使用 jmh 完成的基准测试表明,对该函数的 JNI 调用比 JNA 快 15 倍。
一个更“复杂”的示例,其中本机函数将 4 个值的整数数组相加,仍然表明 JNI 的性能比 JNA 快 3 倍。减少的优势可能是因为您在 JNI 中访问数组的方式:我的示例创建了一些东西并在每次求和操作期间再次释放它。
代码和测试结果可以在 github上找到。
如果您想要 JNI 性能但又被其复杂性吓倒,您可以考虑使用自动生成 JNI 绑定的工具。例如,JANET(免责声明:我写的)允许您在单个源文件中混合 Java 和 C++ 代码,例如使用标准 Java 语法从 C++ 调用 Java。例如,下面是如何将 C 字符串打印到 Java 标准输出:
native "C++" void printHello() {
const char* helloWorld = "Hello, World!";
`System.out.println(#$(helloWorld));`
}
然后,JANET 将反引号嵌入的 Java 转换为适当的 JNI 调用。
我调查了 JNI 和 JNA 以进行性能比较,因为我们需要决定其中一个来调用项目中的 dll,并且我们有一个实时限制。结果表明,JNI 比 JNA 具有更高的性能(大约 40 倍)。也许在 JNA 中有一个提高性能的技巧,但对于一个简单的例子来说它非常慢。
除非我遗漏了什么,否则 JNA 与 JNI 之间的主要区别不是 JNA 不能从本机(C)代码调用 Java 代码吗?
在我的特定应用程序中,JNI 被证明更容易使用。我需要从串行端口读取和写入连续流——仅此而已。与其尝试学习 JNA 中非常复杂的基础设施,我发现在 Windows 中使用仅导出六个函数的专用 DLL 对本机接口进行原型设计要容易得多:
- DllMain(需要与 Windows 交互)
- OnLoad(只做一个 OutputDebugString,这样我就可以知道 Java 代码何时附加)
- OnUnload(同上)
- Open(打开端口,启动读写线程)
- QueueMessage(排队数据以供写入线程输出)
- GetMessage(等待并返回自上次调用以来读取线程收到的数据)