使用 JNI 代替 JNA 调用本机代码?

与 JNI 相比,JNA 似乎更容易用于调用本机代码。在什么情况下你会使用 JNI 而不是 JNA?

50523 次浏览

这不是一个直接的答案,我也没有使用 JNA 的经验,但是,当我看到 使用 JNA 的项目的名字像 SVNKit,IntelliJ IDEA,NetBeans IDE 等,我倾向于相信它是一个相当不错的库。

实际上,当我不得不使用 JNA 而不是 JNI 时,我肯定会使用 JNA,因为它看起来确实比 JNI 简单(JNI 有一个枯燥的开发过程)。可惜 JNA 当时没有被释放。

  1. You are writing code a few years ago before there was JNA or are targeting a pre 1.4 JRE.
  2. 您正在使用的代码不在 DLL SO 中。
  3. 您正在处理与 LGPL 不兼容的代码。

这只是我的第一反应,虽然我不是一个重度使用者。如果您想要一个比它们提供的更好的接口,那么您似乎可以避免 JNA,但是您可以使用 Java 编写相应的代码。

  1. JNA does not support mapping of c++ classes, so if you're using c++ library you will need a jni wrapper
  2. 如果你需要大量的内存拷贝。例如,你调用一个方法返回一个大字节缓冲区,你改变了其中的一些内容,然后你需要调用另一个使用这个字节缓冲区的方法。这需要您将这个缓冲区从 c 复制到 java,然后将它从 java 复制回 c。在这种情况下,jni 将在性能上获胜,因为您可以在 c 中保留和修改这个缓冲区,而无需复制。

这些就是我遇到的问题。也许还有更多。但是在一般情况下,JNA 和 jni 之间的性能没有那么大的差别,所以无论在什么地方可以使用 JNA,都要使用它。

剪辑

这个答案似乎相当受欢迎,因此这里有一些补充:

  1. If you need to map C++ or COM, there is a library by Oliver Chafic, creator of JNAerator, called BridJ. It is still a young library, but it has many interesting features:
    • 动态 C/C + +/COM 互操作: 调用 C + + 方法,创建 C + + 对象(以及来自 Java 的子类 C + + 类!)
    • 使用泛型的简单类型映射(包括更好的指针模型)
    • 全面支持 JNAerator
    • 适用于 Windows,Linux,MacOSX,Solaris,Android
  2. 至于内存复制,我相信 JNA 支持直接的 ByteBuffers,因此可以避免内存复制。

因此,我仍然认为,只要有可能,最好使用 JNA 或 BridJ,如果性能很关键,最好还是恢复为 jni,因为如果需要频繁调用本机函数,性能损失是显而易见的。

很难回答这样一个一般性的问题。我认为最明显的区别是,使用 JNI 时,类型转换是在 Java/本机边界的本机端实现的,而使用 JNA 时,类型转换是在 Java 中实现的。如果您已经对用 C 编程感到非常舒服,并且必须自己实现一些本机代码,那么我假设 JNI 看起来不会太复杂。如果您是一名 Java 程序员,并且只需要调用第三方本机库,那么使用 JNA 可能是避免 JNI 可能不那么明显的问题的最简单的方法。

Although I've never benchmarked any differences, I would because of the design, at least suppose that type conversion with JNA in some situations will perform worse than with JNI. For example when passing arrays, JNA will convert these from Java to native at the beginning of each function call and back at the end of the function call. With JNI, you can control yourself when a native "view" of the array is generated, potentially only creating a view of a part of the array, keep the view across several function calls and at the end release the view and decide if you want to keep the changes (potentially requiring to copy the data back) or discard the changes (no copy required). I know you can use a native array across function calls with JNA using the Memory class, but this will also require memory copying, which may be unnecessary with JNI. The difference may not be relevant, but if your original goal is to increase application performance by implementing parts of it in native code, using a worse performing bridge technology seems not to be the most obvious choice.

除非我遗漏了什么,JNA 和 JNI 之间的主要区别不是在于 JNA 不能从本机(C)代码调用 Java 代码吗?

顺便说一下,在我们的一个项目中,我们保留了一个非常小的 JNI 脚印。我们使用协议缓冲来表示我们的域对象,因此只有一个本机函数来连接 Java 和 c (当然那个 C 函数会调用一大堆其他函数)。

我调查了 JNI 和 JNA 进行性能比较,因为我们需要决定其中一个来调用项目中的 dll,而且我们有实时限制。结果表明,JNI 具有比 JNA 更好的性能(约40倍)。也许在 JNA 中存在提高性能的技巧,但是对于一个简单的示例来说,它非常慢。

如果希望获得 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 做了一些简单的基准测试。

正如其他人已经指出的,JNA 是为了方便。在使用 JNA 时,不需要编译或编写本机代码。JNA 的本地库加载器也是我所见过的最好/最容易使用的加载器之一。不幸的是,你似乎不能在 JNI 上使用它。(这就是我编写 an alternative for System.loadLibrary()的原因,它使用 JNA 的路径约定,并支持从类路径(即 jar)无缝加载。)

然而,JNA 的性能可能比 JNI 差得多。我做了一个非常简单的测试,将一个简单的本机整数增量函数称为“ return arg + 1;”。使用 jmh 完成的基准测试表明,对该函数的 JNI 调用比 JNA 快15倍。

A more "complex" example where the native function sums up an integer array of 4 values still showed that JNI performance is 3 times faster than JNA. The reduced advantage was probably because of how you access arrays in JNI: my example created some stuff and released it again during each summing operation.

代码和测试结果可以找到 在 Github

In my specific application, JNI proved far easier to use. I needed to read and write continuous streams to and from a serial port -- and nothing else. Rather than try to learn the very involved infrastructure in JNA, I found it much easier to prototype the native interface in Windows with a special-purpose DLL that exported just six functions:

  1. DllMain (需要与 Windows 接口)
  2. OnLoad (只执行一个 OutputDebugString,这样我就可以知道 Java 代码何时附加)
  3. OnUnload (ditto)
  4. 打开(打开端口,开始读写线程)
  5. QueueMessage (将数据排队以供写线程输出)
  6. GetMessage (等待并返回读线程自上次调用以来接收的数据)