【问题标题】:Use JNI instead of JNA to call native code?使用 JNI 代替 JNA 调用原生代码?
【发布时间】:2019-10-05 19:17:20
【问题描述】:

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

【问题讨论】:

  • 我们选择 JNA 而不是 JNI 的一个重要因素是,JNA 不需要对我们的原生库进行任何修改。必须自定义原生库才能使用 Java 对我们来说是个大问题。

标签: java java-native-interface native jna


【解决方案1】:
  1. JNA 不支持 c++ 类的映射,因此如果您使用 c++ 库,则需要一个 jni 包装器
  2. 如果您需要大量内存复制。例如,您调用一个返回大字节缓冲区的方法,您更改其中的某些内容,然后您需要调用另一个使用此字节缓冲区的方法。这将要求您将此缓冲区从 c 复制到 java,然后将其从 java 复制回 c。在这种情况下,jni 将在性能上获胜,因为您可以在 c 中保留和修改此缓冲区,而无需复制。

这些是我遇到的问题。也许还有更多。但总的来说 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,所以可以避免内存复制。

所以,我仍然认为,只要有可能,最好使用 JNA 或 BridJ,如果性能至关重要,则恢复为 jni,因为如果您需要频繁调用原生函数,性能损失是显而易见的。

【讨论】:

  • 我不同意,JNA 有很多开销。虽然它的便利性值得在非时间关键代码中使用
  • 在将 BridJ 用于 Android 项目之前,我建议您谨慎行事,直接引用其 download page:“BridJ 部分适用于 Android/arm 模拟器(使用 SDK),并且可能即使在实际设备上(未经测试)。"
  • @StockB:如果你有一个带有 api 的库,你必须在其中传递 C++ 对象,或者调用 C++ 对象上的方法,JNA 不能这样做。它只能调用 vanilla C 方法。
  • 据我了解,JNI只能调用全局的JNIEXPORT函数。我目前正在探索 JavaCpp 作为一个选项,它使用 JNI,但我认为 vanilla JNI 不支持这一点。有没有办法使用我忽略的 vanilla JNI 调用 C++ 成员函数?
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:
    1. 您是在几年前在 JNA 出现之前编写代码,或者您的目标是 1.4 之前的 JRE。
    2. 您正在使用的代码不在 DLL\SO 中。
    3. 您正在处理与 LGPL 不兼容的代码。

    这只是我能想到的,尽管我不是任何一个的重度用户。如果您想要一个比他们提供的接口更好的接口,您似乎也可以避免使用 JNA,但您可以在 java 中编写代码。

    【讨论】:

    • 我不同意 2 - 将静态库转换为动态库很容易。请参阅我的问题stackoverflow.com/questions/845183/…
    • 从 JNA 4.0 开始,JNA 在 LGPL 2.1 和 Apache License 2.0 下获得双重许可,您可以选择哪种方式
    【解决方案4】:

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

    【讨论】:

    • 所以,我们有消息传递而不是方法调用。我在 JNI 和 JNA 以及 BridJ 上投入了相当多的时间,但很快,它们都变得有点太吓人了。
    【解决方案5】:

    这不是一个直接的答案,我没有使用 JNA 的经验,但是当我查看 Projects Using JNA 并看到 SVNKit、IntelliJ IDEA、NetBeans IDE 等名称时,我倾向于认为它是一个相当不错的库.

    实际上,我绝对认为我会在必要时使用 JNA 而不是 JNI,因为它确实看起来比 JNI 更简单(开发过程很无聊)。太糟糕了,此时 JNA 还没有发布。

    【讨论】:

      【解决方案6】:

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

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

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

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

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

      【讨论】:

        【解决方案7】:

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

        【讨论】:

          【解决方案8】:

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

          【讨论】:

            【解决方案9】:

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

            【讨论】:

            • 你可以。带有对应于 C 端函数指针的回调类。 void register_callback(void (*)(const int));将被映射到 public static native void register_callback(MyCallback arg1);其中 MyCallback 是一个扩展 com.sun.jna.Callback 的接口,具有单个方法 void apply(int value);
            • @Augusto 这也可以是评论
            【解决方案10】:

            在我的特定应用程序中,JNI 被证明更容易使用。我需要从串行端口读取和写入连续流——仅此而已。与其尝试学习 JNA 中非常复杂的基础架构,我发现在 Windows 中使用仅导出六个函数的专用 DLL 对本机接口进行原型设计要容易得多:

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2013-10-05
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多