寻找一种方便的方式来从C++中调用Java

31
似乎大部分与JNI(Java Native Interface)相关的文档或帮助库都涉及从Java调用本地代码。即使它有更多的功能,这似乎是其主要用途。
我想主要在相反的方向上工作:通过添加一些Java库来修改现有(相当大的)可移植C++程序。例如,我想通过JDBC调用数据库,通过JMS调用消息队列系统,发送电子邮件,或者调用自己的Java类等。但是使用原始的JNI会很不愉快且容易出错。
因此,我最好能够编写可以像C++/CLI调用CLR类一样轻松调用Java类的C++代码。类似于:
using namespace java::util::regex; // namespaces mapped

Pattern p = Pattern.compile("[,\\s]+");

array<java::lang::String> result = 
    p.split("one,two, three   four ,  five");

for (int i=0; i < result.length(); i++)
    std::cout << result[i] << std::endl;

这样,我就不需要手动通过传递名称和奇怪的签名字符串来获取方法ID的工作,并且可以避免由于调用方法的未经检查的API导致的编程错误。实际上,它看起来很像等效的Java。
注意:我仍在谈论使用JNI!作为底层技术,它非常适合我的需求。它是“进程内”且高效的。我不想在单独的进程中运行Java并进行RPC调用。JNI本身很好。我只是想要一个愉快的接口。
必须有一个代码生成工具来生成相当的C++类、命名空间、方法等,以完全匹配我指定的一组Java类所公开的内容。生成的C++类将:
  • 拥有接受类似包装过的参数的成员函数,然后进行必要的JNI操作进行调用。
  • 以相同方式包装返回值,以便我可以自然地链接调用。
  • 维护每个类的静态方法ID缓存,以避免每次查找。
  • 完全线程安全,可移植,开源。
  • 在每个方法调用后自动检查异常并产生std C++异常。
  • 也适用于我以通常的JNI方式编写本机方法,但需要调用其他Java代码时。
  • 数组应在基本类型和类之间完全一致地工作。
  • 毫无疑问,当引用需要在本地引用框架之外存活时,需要类似全局的东西来包装引用-同样适用于所有数组/对象引用。

是否存在这样一个免费、开源、可移植的库/工具?还是我在做梦?

注意:我发现this existing question,但那个问题中的OP要求远不如我这么苛刻...

更新:关于SWIG的评论引导我看到了this previous question,它似乎主要涉及相反的方向,因此不能满足我的需求。

重要提示:

  • 这是关于能够编写操作Java类和对象的C++代码,而不是相反的(请看标题!)
  • 我已经知道JNI的存在(请看问题!),但手写JNI API的代码冗长、重复、容易出错,在编译时没有类型检查等。如果你想缓存方法ID和类对象,它甚至更加冗长。我希望自动生成C++包装类来为我处理所有这些事情。

更新:我已经开始着手解决自己的问题:

https://github.com/danielearwicker/cppjvm

如果已经存在,请告诉我!

注意:如果您考虑在自己的项目中使用此代码,请放心,但请记住,现在的代码只有几个小时的历史,并且我目前只编写了三个非常简单的测试。


1
虽然我只在很久以前使用过它来处理C和Python,但有SWIG - user166390
更新:SWIG 显然不支持此功能。 - Daniel Earwicker
你想在哪个编译器/解释器中执行你的程序,C还是Java?如果是Java,使用JNI无法进行反向操作。 - Naved
请访问http://gcc.gnu.org/onlinedocs/gcj/About-CNI.html 以了解有关CNI的信息。 - MarianP
@MarianP - CNI是GCJ的一个功能,它是支持Java子集和Java库子集的编译器。我想使用任何第三方Java库在真正支持的Java实现上运行。 - Daniel Earwicker
显示剩余3条评论
9个回答

17

是的,有现成的工具可以生成Java类的C++包装器。这使得在C++中使用Java API更加透明和愉悦,且成本和风险更低。

我最常用的一个工具是JunC++ion。它很成熟、功能强大且稳定。主要作者非常友好、反应迅速。不幸的是,它是商业产品,价格昂贵。

Jace是一个带有BSD许可证的免费、开源的工具。我已经多年没有用过jace了。看起来仍然有一些活跃的开发。(我仍然记得原作者十多年前在USENET上发布的帖子,基本上问了你正在问的同样问题。)

如果您需要支持从Java到C++的回调,定义实现Java接口的C++类会很有帮助。至少JunC++ion允许您将这样的C++类传递给接受回调的Java方法。我上次尝试jace时,它不支持这个功能--但那是七年前的事了。


谢谢。我从Jace源代码中看到它将每个引用都转换为全局变量!这不是一个好主意。JNI故意将其作为选项:全局变量比本地变量更重,而实际程序中绝大多数变量都是本地变量。没有必要在析构函数中“释放”本地变量,因为JVM会处理它。您只需要一个RAII类来封装PushLocalFrame/PopLocalFrame。JunC++ion采取什么方法? - Daniel Earwicker
2
顺便说一句,我用谷歌搜索“C++ Java JNI Wrapper”这些词,现在看到JunC++ion在第22页的结果中了!还没有发现Jace... - Daniel Earwicker
如果搜索基于质量,JunC++ion将出现在第一页。我现在无法访问JunC++ion。我猜在某些情况下需要全局引用并且对于其他情况则足够了,但这只是一个猜测。我会看看是否能得到作者的回复。 - Andy Thomas
Alex Krapf的下面的回答涉及全局/本地引用。 - Andy Thomas

8
我是Codemesh语言集成产品(包括JunC++ion)的主要架构师之一。我们从1999年开始进行这种集成,效果非常好。最大的问题不是JNI部分。JNI很烦琐,难以调试,但一旦你做对了,它基本上就可以一直工作。每隔一段时间,你会被JVM或操作系统更新打断,然后你就需要微调产品,但总体来说它是稳定的。
最大的问题是类型系统映射和通用可用性与目标解决方案之间的权衡。例如,您指出不喜欢JACE将所有对象引用视为全局变量的事实。我们也是这样做的(带有一些逃生口),因为事实证明,这是对95%的客户最有效的行为,即使它会影响性能。如果您要发布API或产品,则必须选择默认选项,使大多数人的工作正常运行。选择本地引用作为默认选项是错误的,因为越来越多的人正在编写多线程应用程序,并且许多Java API从其他语言中使用时本质上是多线程的,具有异步回调等功能。
我们还发现,确实希望为用户提供基于GUI的代码生成器来创建集成规范。一旦他们指定了规范,您可以使用CLI版本将其集成到每晚的构建中。
祝你的项目好运。正确完成这项工作需要很多努力。我们花了几年时间,并且仍在不断改进。

谢谢。关于合理的默认选择,我完全同意 - 在我的设计中,这是最重要的事情。对于我的当前项目,有选择使用本地变量将非常重要,共享数据将非常少见,因此我选择使用 java::lang::Stringglobal<java::lang::String> 作为本地变量和全局变量的语法。 - Daniel Earwicker

4
我曾经遇到过类似的问题,最终自己解决了,希望这能帮助到其他人。
这里有一个链接:https://github.com/mo22/jnipp 它拥有小的运行时占用(小于30kb),可以管理引用,并支持生成Java类接口。例如:LocalRef> stringArray; 然后使用stringArray[1]->getBytes() 或者其他方法。

2

从C++调用Java。

您可以随心所欲,但必须让Java控制。我的意思是,您创建调用本地代码的Java线程,然后它们会阻塞,等待您的本地代码提供任务。您可以创建尽可能多的Java线程以完成足够的工作/吞吐量。

因此,您的C++应用程序启动后,创建一个JVM / JavaVM(按照文档方式,qtjambi代码库中存在示例),然后执行通常的JNI初始化和System.loadLibrary()并提供具有“本地”链接的JAR文件。然后,您初始化一堆线程并调用一些JNI代码(由您创建),它们可以在其中等待您的C ++代码给它们一些任务。

您的C ++代码(可能来自另一个线程)然后设置并传递所有需要的信息给其中一个被阻止和等待的Java线程工作者,然后它被命令运行,然后可能返回纯Java代码执行工作并返回结果。

...

可以从C ++代码设置,创建和包含JavaVM实例。这可以强制将您自己的CLASSPATH / JAR馈送到设置在C ++程序内部的封装环境中。

如您已经发现的那样,在http://download.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.html中概述了这个过程。

...

在QtJambi项目中有一种C ++ => Java JNI生成器(我参与并帮助维护)。这对于Qt工具包来说相当定制化,但本质上它将一堆C ++头文件转换为一组C ++ .cpp / .h文件和* .java文件,以提供链接和外壳封装对象,使竞争的内存分配方案可以很好地协同工作。也许可以从中得到一些启示。

这确实是您所要求的概念证明,生成器只是包含在qtjambi项目中(但可以通过一些工作变得独立),而且这是LGPL许可的(开源)。Qt工具包不是一个小的API,但它可以生成100多个类,以覆盖高%的API(> 85%,几乎100%的核心/ GUI部分)。

希望对您有所帮助。


你关于线程的回答对我来说毫无意义。我已经可以使用纯JNI做比那更简单的事情了。我只需要初始化JVM,要求它加载一个类,调用它的构造函数,调用它的方法。就这些。 - Daniel Earwicker
QtJambi库是一个项目,允许Java代码使用Qt库,即Java调用C++。它是否读取现有的Java类并生成相应的C++包装器类,以便可以轻松地从C++中调用Java类? - Daniel Earwicker
1
线程问题是关于如何设置一个进程,以便C++可以随时安全地调用Java。 Qt库的部分内容很复杂,它是一个C++库,也需要回调到Java。您可以注册在C++中运行但偶尔调用Java中的事物的回调/事件处理程序/线程。 Java具有纯Java和C/C++的API。但是C/C++没有用于在Java中执行操作的API。当Java已经有一个C++ API(即JNI)时,制作Java事物的C++包装器毫无意义。 - Darryl Miles
也许我表达得不太好或者有些问题。 - Daniel Earwicker
这是一个非常有趣的想法,它似乎只需要在C++部分进行一些重构,但其余部分大多基于纯JNI,这非常稳定。看到一些真实的例子会很不错,也许其他人已经尝试过了? - Cray

1

我在不同的操作系统上遇到了许多困难,包括JNI的使用、32/64位架构的处理以及确保正确的共享库被找到和加载。

我也发现CORBA(MICO和JacORB)很难使用。

我没有找到有效的方法从C/C++调用Java,我的首选解决方案是将Java代码作为以下两种情况之一运行:

  1. 一个独立的程序,我可以轻松地从C/C++程序中运行它,使用命令java -cp myjar.jar org.foo.MyClass。我猜这对你的情况来说太简单了。

  2. 作为一个迷你服务器,在TCP/IP套接字上接受来自C/C++程序的请求,并通过该套接字返回结果。这需要编写网络和序列化函数,但将C/C++和Java进程分离开来,您可以清楚地确定任何问题是在C++端还是Java端。

  3. 作为Tomcat中的Servlet,并从我的C/C++程序中进行HTTP请求(其他Servlet容器也可以)。这也需要编写网络和序列化函数。这更像SOA。


谢谢。到目前为止,我已经在Windows和Linux上成功地使用JNI,没有遇到任何问题 - 它们似乎基本相同。CORBA与我所需的完全不同。将所有调用进行进程外编排似乎就像手写版本的SOA(或CORBA)中的内容,而且所有这些都有很大的额外开销和更多的复杂性。 - Daniel Earwicker
与此同时,如果每个方法调用的咒语(传递方法名称和签名,缓存ID,确定正确的Call [Static] MethodT类型,检查异常)可以干净地自动化,JNI将是完美的轻量级解决方案。这就是我想要的全部。 - Daniel Earwicker
另一个商业产品:http://www.javain.com/javain/oojni.jsp?cat=oojni&sub=whatIs - Daniel Earwicker

1

我应该更新我的问题,以排除使用RPC连接到进程外的服务器。 - Daniel Earwicker

0

也许这个问题有点小题大做,但 CORBA 不就是为此而建立的吗?


3
从这个链接中可以看到:"C++映射是出了名的困难;这种映射要求程序员学习复杂而混淆的数据类型,这些类型早于C++标准模板库"。听起来很不错! :) - Daniel Earwicker

0

时间已经很晚了。这两个链接似乎只提供了其他人已经给出的相同信息...晚安 :) - Axel

0

回答自己的问题:

http://java4cpp.kapott.org/

似乎不是一个活跃的项目。作者建议不要在JDK 1.5或更高版本中使用。

它似乎存在一个严重的问题:它将裸指针传递给其包装对象:

java::lang::Integer* i = new java::lang::Integer("10");

delete i; // don't forget to do this!

这也导致了一个更微妙的问题,为了表示赋值兼容性(例如,将一个类作为其实现的接口的子类型),包装器必须彼此继承。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接