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


115

与JNI相比,JNA似乎更易于调用本机代码。在什么情况下,您将在JNA上使用JNI?


我们选择JNA而不是JNI的一个重要因素是JNA不需要对我们的本机库进行任何修改。为了能够与Java一起使用而必须自定义本机库对我们来说是一个大难题。
kvr

Answers:


126
  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支持直接ByteBuffer,因此可以避免内存复制。

因此,我仍然相信,只要性能至关重要,最好尽可能使用JNA或BridJ,并恢复为jni,因为如果需要频繁调用本机函数,那么性能会受到明显影响。


6
我不同意,JNA有很多开销。尽管值得在非时间紧迫的代码中使用它的方便性
Gregory Pakosz 2010年

3
我建议在将BridJ用于Android项目之前要谨慎,直接从其下载页面中引用:“ BridJ在Android / arm模拟器(使用SDK)上可能部分工作,甚至在实际设备上(未
试用

1
@StockB:如果您有一个带有api的库,则必须在其中传递C ++对象,或在C ++对象上调用方法,JNA无法做到这一点。它只能调用香草C方法。
丹尼斯·图斯基

2
据我了解,JNI只能调用全局JNIEXPORT函数。我目前正在探索JavaCpp作为使用JNI的选项,但是我不认为香草JNI支持这一点。有没有一种方法可以使用我忽略的原始JNI调用C ++成员函数?
13年


29

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

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


8
  1. 几年前,您正在编写代码,而JNA尚未发布,或者目标是1.4 JRE之前的版本。
  2. 您正在使用的代码不在DLL \ SO中。
  3. 您正在处理与LGPL不兼容的代码。

尽管我都不是其中的重任,但这只是我能想到的。如果您想要一个比其提供的接口更好的接口,似乎也可以避免使用JNA,但是可以在Java中对此进行编码。


9
我不同意2-将静态库转换为动态库很容易。见我的问题abput它stackoverflow.com/questions/845183/...
JB。

3
从JNA 4.0开始,JNA在LGPL 2.1和Apache License 2.0下都获得了双重许可,您可以根据自己的需要选择
sbarber2 '16

8

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


5
因此,我们没有消息调用,而是有消息传递。我已经在JNI和JNA以及BridJ上投入了很多时间,但是很快,它们都变得有些吓人。
Ustaman Sangat'4

5

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

实际上,我绝对认为我不得不使用JNA而不是JNI,因为它看上去确实比JNI(开发过程很无聊)简单。太糟糕了,JNA目前没有发布。


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调用。


3

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

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

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

一个更“复杂”的示例(本机函数将4个值的整数数组求和)仍然表明,JNI性能比JNA快3倍。降低的优势可能是由于您如何访问JNI中的数组:我的示例创建了一些东西,并在每次求和操作期间再次释放了它们。

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


2

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


1

除非我有所遗漏,否则JNA与JNI之间的主要区别是否就是使用JNA不能从本地(C)代码调用Java代码?


6
您可以。具有与C端的函数指针相对应的回调类。void register_callback(void(*)(const int)); 将被映射到公共静态本机void register_callback(MyCallback arg1); 其中MyCallback是使用单个方法void apply(int value);扩展com.sun.jna.Callback的接口。
乌斯塔曼·桑加特

@Augusto也可以发表评论
swiftBoy 2012年

0

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

  1. DllMain(需要与Windows交互)
  2. OnLoad(只有OutputDebugString可以使我知道何时附加Java代码)
  3. OnUnload(同上)
  4. 打开(打开端口,启动读写线程)
  5. QueueMessage(排队数据以供写线程输出)
  6. GetMessage(等待并返回自上次调用以来读取线程接收的数据)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.