在本教程中,我们将探讨如何在不编写任何 C 代码的情况下从 Java 调用 Go 函数,并利用Java Native Access (JNA)库来弥合两种语言之间的差距。
通过遵循本教程中的指导,我们可以使用 Java Native Access (JNA) 库轻松地将 Go 函数集成到 Java 应用程序中,而无需编写任何 C 代码。此方法将 Go 的性能和并发性与 Java 相结合,简化了集成并加快了开发速度。
关键因素包括确保 Java 和 Go 数据类型之间的兼容性、正确管理内存以避免泄漏以及设置强大的错误处理。通过将 Java 的生态系统与 Go 的性能优化相结合,开发人员可以创建高效而强大的应用程序。
JNA 比 JNI 有几个优点,例如不需要 C 代码、支持跨平台开发、简化本机集成过程以加快实施速度。
总之,使用 JNA 将 Go 函数集成到 Java 中是一种有效且直接的方法,可在简化开发的同时提高性能。
使用 JNA 连接 Java 和 Go
传统上,从 Java 调用本机代码需要用 C编写Java 本机接口 (JNI)代码,这增加了复杂性和开销。但是,随着 Java 本机访问 (JNA) 库的出现,可以直接从 Java 调用本机共享库,而无需深入研究 C 代码。这种方法简化了集成过程,并允许开发人员在 Java 应用程序中无缝利用 Go 的功能。
为了了解这种集成的工作原理,首先,我们将探讨 Java 本机访问 (JNA) 库在连接 Java 和 Go 方面的作用。具体来说,JNA 提供了一种从 Java 代码调用本机共享库中函数的简单方法。通过将 Go 代码编译成共享库并导出必要的函数,Java 可以与 Go 函数交互,就好像它们是其自身生态系统的一部分一样。
本质上,这个过程涉及编写 Go 函数、将它们编译成共享库,然后使用 JNA 创建映射到这些函数的相应 Java 接口。
设置项目
在深入实施之前,必须正确设置项目环境。这涉及配置集成所需的构建工具和依赖项。
在我们的例子中,我们需要以下组件:
- Java 开发工具包(JDK):用于编译和运行 Java 代码。
- Go 编程语言:用于编写和编译 Go 代码。
- Java 本机访问 (JNA) 库:作为依赖项包含在 Maven 项目中。
- 构建工具:Java 的 Maven 和 Go 代码的 Go 编译器。
<dependency> |
从 Java 调用 Go 函数
为了演示如何将 Go 函数集成到 Java 应用程序中,我们将创建一个简单的程序,其中 Java 调用一个 Go 函数,该函数将消息打印到控制台。实现涉及几个关键步骤:编写 Go 函数、将其编译为共享库,然后编写使用 Java Native Access (JNA) 库调用 Go 函数的 Java 代码。
构建 Go 共享库
首先,我们需要正确定义 Go 代码。要使共享库中可以访问 Go 函数,我们必须使用 C 链接将其导出。这可以通过包含import “C”语句并在函数定义之前放置//export指令来实现。此外,在 Go 中构建共享库时需要一个空的 main 函数。
让我们创建一个名为hello.go的文件:
package main |
让我们重点介绍一下上面代码中最重要的部分:
- 导入“C”语句启用CGO,允许使用 C 特性和导出函数。
- //export SayHello指令告诉 Go 编译器使用 C 链接导出SayHello函数。
- 将 Go 代码编译为共享库时,空的main函数是必需的。
- 编写 Go 函数后,下一步是将 Go 代码编译为共享库。此操作使用带有-buildmode=c-shared选项的 Go build命令完成,该命令会生成共享库文件(例如Linux 上的libhello.so或 Windows 上的libhello.dll)。
根据我们使用的操作系统,我们需要执行不同的命令来编译共享库。
对于基于Linux的操作系统,我们可以使用以下命令:
go build -o libhello.so -buildmode=c-shared hello.go
对于 Windows,为了构建dll库,我们应用下一个命令:
go build -o libhello.dll -buildmode=c-shared hello.go
对于 macOS,为了获取dylib库,我们执行相应的命令:
go build -o libhello.dylib -buildmode=c-shared hello.go
因此,我们应该在当前目录中找到共享库。
为了方便起见,所有这些脚本都与README.md文件一起包含在源代码中。
创建 JNA 接口
下一步是进行 Java 方面的集成。在 Java 应用程序中,我们利用 Java Native Access (JNA) 库来加载共享库并调用 Go 函数。首先,我们定义一个扩展com.sun.jna.Library 的Java 接口,声明与导出的 Go 函数相对应的方法:
import com.sun.jna.Library; |
在这个接口中,GoLibrary扩展了JNA 的标记接口Library ,并且SayHello方法签名与从共享库导出的 Go 函数相匹配。
接下来,我们编写使用该接口的 Java 应用程序来加载共享库并调用 Go 函数。
import com.sun.jna.Native; |
在此代码中,Native.load 加载共享库libhello.so(省略 lib 前缀和 .so 扩展名),创建 GoLibrary 的实例以访问导出的函数,并调用SayHello方法,该方法调用 Go 函数并将消息打印到控制台。
运行 Java 应用程序时,确保共享库在库路径中可访问非常重要。这可以通过将共享库放在与 Java 应用程序相同的目录中或通过设置适当的环境变量来实现:
对于基于Linux的系统,我们通过调用export命令来定义环境变量:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
对于 Windows,我们需要将包含libhello.dll文件的目录添加到PATH环境变量中。这可以在命令提示符中使用以下命令完成(或通过系统环境设置永久完成):
set PATH=%PATH%;C:\path\to\directory
对于 macOS,我们使用与 Linux 类似的命令:
export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:.
最后,如果一切设置正确,我们应该能够运行我们的应用程序并获得以下输出:
Hello Baeldung from Go!
传递参数和返回值
基于这个简单示例,我们可以通过将参数传递给 Go 函数并将值返回给 Java 应用程序来增强集成。这演示了如何在 Java 和 Go 之间交换数据。
首先,我们修改 Go 代码,使其包含一个将两个整数相加并返回结果的函数。在 Go 代码中,我们定义AddNumbers函数来接受两个C.int类型的整数并返回一个C.int。
//export AddNumbers |
更新 Go 代码后,我们需要重新编译共享库以包含新功能。
接下来,我们更新 Java 接口以包含AddNumbers函数。我们定义一个与 Go 函数相对应的接口,并指定具有适当参数和返回类型的方法签名。
public interface GoLibrary extends Library { |
因此,我们可以调用AddNumbers函数并传递int参数:
public static void main(String[] args) { |
运行应用程序后,我们应该在输出中看到计算结果:
Result is 5
处理复杂数据类型
除了简单的数据类型外,通常还需要处理更复杂的数据类型,例如字符串。要将字符串从 Java 传递到 Go 并接收字符串,我们需要在 Go 代码中处理指向 C 字符串的指针,并在 Java 中对其进行适当的映射。
首先,我们将实现一个接受字符串并返回问候消息的 Go 函数。在 Go 代码中,我们定义Greet函数,它接受*C.char并返回*C.char。我们使用C.GoString将 C 字符串转换为 Go 字符串,并使用C.CString将 Go 字符串转换回 C 字符串。
//export Greet |
添加新功能后,我们需要重新编译共享库。
接下来,我们需要更新 Java 接口以包含Greet函数。由于 Go 函数返回 C 字符串,我们将使用JNA 提供的Pointer类来处理返回值。
public static void main(String[] args) { |
在此代码中,使用参数“Alice”调用Greet方法,返回的Pointer检索字符串,Native.free释放 Go 函数分配的内存。
如果我们运行该应用程序,我们可能会得到以下结果:
Hello, Alice!