使用JNI而不是JNA来调用本机代码?

时间:2009-10-12 19:26:13

标签: java java-native-interface native jna

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

10 个答案:

答案 0 :(得分:122)

  1. JNA不支持c ++类的映射,所以如果你使用的是c ++库,则需要一个jni包装器
  2. 如果需要大量内存复制。例如,您调用一个返回大字节缓冲区的方法,更改其中的某些内容,然后需要调用另一个使用此字节缓冲区的方法。这将要求您将此缓冲区从c复制到java,然后将其从java复制回c。在这种情况下,jni将在性能上获胜,因为您可以在c中保留和修改此缓冲区,而无需复制。
  3. 这些是我遇到的问题。也许还有更多。但总的来说,jna和jni之间的性能并没有那么不同,所以无论你在哪里使用JNA,都要使用它。

    修改

    这个答案似乎很受欢迎。所以这里有一些补充:

    1. 如果你需要映射C ++或COM,那么JNAerator的创建者Oliver Chafic就有一个名为BridJ的库。它仍然是一个年轻的图书馆,但它有许多有趣的特点:
      • 动态C / C ++ / COM互操作:调用C ++方法,创建C ++对象(以及从Java子类化C ++类!)
      • 使用泛型的直接类型映射(包括更好的指针模型)
      • 完整的JNAerator支持
      • 适用于Windows,Linux,MacOS X,Solaris,Android
    2. 至于内存复制,我相信JNA支持直接ByteBuffers,因此可以避免内存复制。
    3. 所以,我仍然认为,只要有可能,最好使用JNA或BridJ,如果性能至关重要,则恢复为jni,因为如果需要经常调用本机函数,性能就会受到影响。

答案 1 :(得分:29)

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

虽然我从来没有对任何差异进行基准测试,但我会因为设计,至少假设在某些情况下使用JNA的类型转换将比使用JNI更糟糕。例如,当传递数组时,JNA会在每个函数调用开始时将这些从Java转换为本机,并在函数调用结束时返回。使用JNI,您可以在生成数组的本机“视图”时控制自己,可能只创建数组的一部分视图,保持多个函数调用的视图,最后释放视图并决定是否要保持更改(可能需要复制数据)或丢弃更改(无需复制)。我知道你可以使用Memory类在JNA的函数调用中使用本机数组,但是这也需要内存复制,这对于JNI来说可能是不必要的。差异可能并不重要,但如果您最初的目标是通过在本机代码中实现部分应用程序性能来提高应用程序性能,则使用性能较差的桥接技术似乎不是最明显的选择。

答案 2 :(得分:8)

  1. 您在几年前编写代码之前有JNA或正在针对1.4之前的JRE。
  2. 您正在使用的代码不在DLL \ SO中。
  3. 您正在处理与LGPL不兼容的代码。
  4. 这只是我能想到的最重要的事情,尽管我不是一个沉重的用户。如果你想要一个比他们提供的界面更好的界面,你似乎可以避免使用JNA,但你可以在java中编写代码。

答案 3 :(得分:7)

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

答案 4 :(得分:5)

这不是一个直接的答案,我没有JNA的经验但是,当我查看Projects Using JNA并看到SVNKit,IntelliJ IDEA,NetBeans IDE等名称时,我倾向于认为它是一个漂亮的体面的图书馆

实际上,我绝对认为我必须使用JNA而不是JNI,因为它确实看起来比JNI(它有一个无聊的开发过程)简单。太糟糕了,JNA此时尚未发布。

答案 5 :(得分:3)

如果您想要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调用。

答案 6 :(得分:3)

我实际上用JNI和JNA做了一些简单的基准测试。

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

然而,JNA的表现可能比JNI的表现差得多。我做了一个非常简单的测试,称为简单的原生整数增量函数"返回arg + 1;"。使用jmh完成的基准测试表明,JNI调用该函数的速度比JNA快15倍。

更多"复杂"例如,本机函数总结了4个值的整数数组,仍然表明JNI性能比JNA快3倍。减少的优势可能是因为您在JNI中访问数组的方式:我的示例创建了一些内容并在每次求和操作期间再次释放它。

可以找到代码和测试结果at github

答案 7 :(得分:2)

我调查了JNI和JNA的性能比较,因为我们需要决定其中一个在项目中调用一个dll并且我们有一个实时约束。结果表明,JNI的性能优于JNA(约40倍)。也许有一个技巧可以在JNA中获得更好的性能,但对于一个简单的例子来说它很慢。

答案 8 :(得分:1)

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

答案 9 :(得分:0)

在我的特定应用程序中,JNI被证明更易于使用。我需要在串行端口之间读取和写入连续流-别无其他。我发现尝试使用仅导出六个功能的专用DLL在Windows中对本机接口进行原型设计要容易得多,而不是尝试学习JNA中涉及非常广泛的基础结构。

  1. DllMain(与Windows交互)
  2. OnLoad(OutputDebugString也一样,这样我就能知道何时附加Java代码)
  3. OnUnload(同上)
  4. 打开(打开端口,启动读写线程)
  5. QueueMessage(排队数据以供写线程输出)
  6. GetMessage(等待并返回自上次调用以来读取线程接收的数据)