51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

用 Java 调用 GoLang 函数

1、简介 {#1简介}

众所周知,Java 和 Go 是两种著名的编程语言,各自在不同的领域表现出色。Java 以其可移植性和广泛的生态系统而闻名,而 Go 则以其简洁性、性能和高效的并发处理而著称。在某些情况下,将两种语言的优势结合起来,可以开发出更强大、更高效的应用程序。

本文将带你了解如何利用 Java Native Access(JNA)库来弥合两种语言之间的差距,从而在不编写任何 C 代码的情况下从 Java 中调用 Go 函数。

2、用 JNA 架起 Java 和 Go 之间的桥梁 {#2用-jna-架起-java-和-go-之间的桥梁}

传统上,从 Java 调用本地代码需要用 C 语言编写 Java 本地接口(JNI)代码,这会增加复杂性和开销。然而,随着 Java Native Access(JNA)库的出现,可以直接从 Java 调用本地共享库,而无需深入研究 C 代码。这种方法简化了集成过程,允许开发人员在 Java 应用程序中无缝利用 Go 的功能。

要理解这种集成是如何工作的,首先,要了解 Java Native Access(JNA)库在连接 Java 和 Go 方面的作用。具体来说,JNA 提供了一种从 Java 代码调用本地共享库函数的直接方法。通过将 Go 代码编译到共享库中并导出必要的函数,Java 可以与 Go 函数进行交互,就像它们是自己生态系统的一部分一样。

本质上,这一过程包括编写 Go 函数,将其编译成共享库,然后使用 JNA 创建映射到这些函数的相应 Java 接口。

2、项目设置 {#2项目设置}

首先,设置项目环境。这包括配置集成所需的构建工具和依赖项。在本例中,我们需要以下组件:

  • Java Development Kit(JDK):用于编译和运行 Java 代码。
  • Go 语言环境:用于编写和编译 Go 代码。
  • Java Native Access(JNA)库:作为 Maven 项目的一个依赖项包含在内。
  • 构建工具:Maven 适用于 Java,Go 编译器适用于 Go 代码。

添加 JNA 库到 maven 依赖:

<dependency>
    <groupId>net.java.dev.jna</groupId>
    <artifactId>jna-platform</artifactId>
    <version>5.15.0</version>
</dependency>

3、用 Java 调用 Go 函数 {#3用-java-调用-go-函数}

我们以一个简单的程序为例,来演示如何将 Go 函数集成到 Java 应用程序中。

其中 Java 将调用 Go 函数向控制台打印一条消息。实现过程包括几个关键步骤:编写 Go 函数,将其编译到共享库中,然后编写使用 Java Native Access(JNA)库调用 Go 函数的 Java 代码。

3.1、构建 Go 共享库 {#31构建-go-共享库}

首先,我们需要正确定义 Go 代码。

要从共享库中访问 Go 函数,我们必须用 C 链接导出该函数。这可以通过在函数定义前加入 import "C" 语句和 //export 指令来实现。此外,在用 Go 构建共享库时,还需要一个空的 main 函数。

创建一个名为 hello.go 的文件:

package main

/*
#include <stdlib.h>
*/
import "C"
import "fmt"

//export SayHello
func SayHello() {
    fmt.Println("Hello Baeldung from Go!")
}

func main() {}

如上:

  • import "C" 语句启用了 CGO,允许使用 C 功能和导出函数。
  • //export SayHello 指令告诉 Go 编译器导出带有 C 语言链接的 SayHello 函数。
  • 在将 Go 代码编译成共享库时,必须使用空的 main 函数。

编写 Go 函数后,下一步是将 Go 代码编译成共享库。这需要使用 Go build 命令,并加上 -buildmode=c-shared 选项,这样就能生成一个共享库文件(例如,Linux 上的 libhello.so 或 Windows 上的 libhello.dll)。

根据我们使用的操作系统,我们需要执行不同的命令来编译共享库。

对于基于 Linux 的操作系统,我们可以使用以下命令:

go build -o libhello.so -buildmode=c-shared hello.go

Windows:

go build -o libhello.dll -buildmode=c-shared hello.go

macOS:

go build -o libhello.dylib -buildmode=c-shared hello.go

构建完毕后,我们应该能在当前目录下找到共享库。

为方便起见,所有这些脚本都与 README.md 文件一起包含在 源码 中。

3.2、创建 JNA 接口 {#32创建-jna-接口}

下一步是进行 Java 方面的集成。在 Java 应用中,我们利用 Java Native Access (JNA)库加载共享库并调用 Go 函数。

首先,定义一个继承 com.sun.jna.Library 的 Java 接口,声明与导出 Go 函数相对应的方法:

import com.sun.jna.Library;

public interface GoLibrary extends Library {
    void SayHello();
}

在该接口中,GoLibrary 继承了 JNA 的标记接口 LibrarySayHello 方法的签名与共享库中导出的 Go 函数相匹配。

接下来,编写 Java 应用,使用该接口加载共享库并调用 Go 函数。

import com.sun.jna.Native;

public class HelloGo {
    public static void main(String[] args) {
        GoLibrary goLibrary = Native.load("hello", GoLibrary.class);
        goLibrary.SayHello();
    }
}

如上,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!

4、传递参数和返回值 {#4传递参数和返回值}

在上述简单示例的基础上,通过向 Go 函数传递参数并向 Java 应用返回值来演示如何在 Java 和 Go 之间交换数据。

首先,修改 Go 代码,加入一个将两个整数相加并返回结果的函数。

在 Go 代码中,我们定义 AddNumbers 函数接受两个 C.int 类型的整数,并返回一个 C.int.

//export AddNumbers
func AddNumbers(a, b C.int) C.int {
    return a + b
}

更新 Go 代码后,我们需要重新编译共享库以包含新函数。

接下来,更新 Java 接口,添加 AddNumbers 函数。

我们定义了一个与 Go 函数相对应的接口,指定了带有适当参数和返回类型的方法签名。

public interface GoLibrary extends Library {
    void SayHello();
    int AddNumbers(int a, int b);
}

因此,我们可以调用 AddNumbers 函数并传递 int 参数:

public static void main(String[] args) {
    GoLibrary goLibrary = Native.load("hello", GoLibrary.class);
    int result = goLibrary.AddNumbers(2, 3);
    System.out.printf("Result is %d%n", result);
}

运行应用后,应该能在输出中看到计算结果:

Result is 5

5、处理复杂数据类型 {#5处理复杂数据类型}

除了简单的数据类型外,通常还需要处理字符串等更复杂的数据类型。要将字符串从 Java 传递到 Go 并接收回字符串,我们需要在 Go 代码中处理指向 C 字符串的指针,并在 Java 中适当地映射它们。

首先,实现一个接受字符串并返回问候信息的 Go 函数。

在 Go 代码中,我们定义了 Greet 函数,它接受一个 *C.char 并返回一个 *C.char。我们使用 C.GoString 将 C 字符串转换为 Go 字符串,并使用 C.CString 将 Go 字符串转换回 C 字符串。

//export Greet
func Greet(name *C.char) *C.char {
    greeting := fmt.Sprintf("Hello, %s!", C.GoString(name))
    return C.CString(greeting)
}

添加新函数后,需要重新编译共享库。

接下来,需要更新 Java 接口以包含 Greet 函数。

由于 Go 函数返回一个 C 字符串,我们要使用 JNA 提供的 Pointer 类来处理返回值。

public static void main(String[] args) {
    GoLibrary goLibrary = Native.load("hello", GoLibrary.class);
    Pointer ptr = goLibrary.Greet("Alice");
    String greeting = ptr.getString(0);
    System.out.println(greeting);
    Native.free(Pointer.nativeValue(ptr));
}

在这段代码中,调用 Greet 方法时使用的参数是 "Alice",返回的 Pointer 会获取字符串,Native.free 会释放 Go 函数分配的内存。

运行该程序,输出如下:

Hello, Alice!

6、总结 {#6总结}

本文介绍了如何使用 Java Native Access(JNA)库轻松地将 Go 函数集成到 Java 应用程序中,而无需编写任何 C 代码。这种方法将 Go 的性能和并发性与 Java 结合起来,简化了集成过程,加快了开发速度。

关键因素包括确保 Java 和 Go 数据类型之间的兼容性,正确管理内存以避免泄漏,并建立健壮的错误处理机制。通过将 Java 的生态系统与 Go 的性能优化相结合,开发人员可以创建高效而强大的应用程序。

与 JNI 相比,JNA 具有多项优势,如无需使用 C 代码、支持跨平台开发,以及简化本地集成流程以加快实现速度。

总之,使用 JNA 将 Go 函数集成到 Java 中是一种有效而直接的方法,既能提高性能,又能简化开发。

完整的代码可以在 这里 找到。


Ref:https://www.baeldung.com/java-golang-invoke-function

赞(0)
未经允许不得转载:工具盒子 » 用 Java 调用 GoLang 函数