從 Java 呼叫 GoLang 函數
一、簡介
眾所周知,Java 和 Go 是兩種著名的程式語言,各自在不同的領域表現出色。 Java 以其可移植性和廣泛的生態系統而聞名,而 Go 則以其簡單性、效能和高效的並發處理而聞名。在某些情況下,結合兩種語言的優勢可以帶來更強大、更有效率的應用程式。
在本教程中,我們將探索如何在不編寫任何 C 程式碼的情況下從 Java 呼叫 Go 函數,利用 Java Native Access (JNA) 程式庫來彌合兩種語言之間的差距。
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. 設定項目
在深入實施之前,正確設定專案環境至關重要。這涉及配置整合所需的建置工具和相依性。
在我們的例子中,我們需要以下元件:
- Java開發工具包(JDK) :用於編譯和執行Java程式碼。
- Go 程式語言:用於編寫和編譯 Go 程式碼。
- Java Native Access (JNA) 函式庫:作為依賴項包含在 Maven 專案中。
- 建置工具:用於 Java 的 Maven 和用於 Go 程式碼的 Go 編譯器。
讓我們將 JNA 庫新增為Maven 依賴項:
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>5.15.0</version>
</dependency>
3. Java呼叫Go函數
為了演示將 Go 函數整合到 Java 應用程式中,我們將創建一個簡單的程序,其中 Java 呼叫將訊息列印到控制台的 Go 函數。實作過程涉及幾個關鍵步驟:編寫Go函數,將其編譯成共用函式庫,然後編寫使用Java Native Access(JNA)函式庫呼叫Go函數的Java程式碼。
3.1.建構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程式碼編譯成共享函式庫。這是使用具有-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
檔案一起包含在原始程式碼中。
3.2.建立 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
擴展了Library
(來自 JNA 的標記介面),並且SayHello
方法簽名與從共享庫導出的 Go 函數相符。
接下來,我們編寫使用該介面載入共用程式庫並呼叫 Go 函數的 Java 應用程式。
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. 傳遞參數和回傳值
在簡單範例的基礎上,我們可以透過將參數傳遞給 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. 處理複雜資料類型
除了簡單的資料類型之外,通常還需要處理更複雜的資料類型,例如字串。要將字串從 Java 傳遞到 Go 並接收傳回的字串,我們需要在 Go 程式碼中處理指向 C 字串的指針,並在 Java 中適當地映射它們。
首先,我們將實作一個接受string
並傳回問候訊息的 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));
}
在此程式碼中,使用參數「Alice」呼叫Greet
方法,傳回的Pointer
檢索字串, Native.free
釋放 Go 函數分配的記憶體。
如果我們運行該應用程序,我們可能會得到以下結果:
Hello, Alice!
六、結論
透過遵循本教學中的指南,我們可以使用 Java Native Access (JNA) 函式庫輕鬆地將 Go 函數整合到 Java 應用程式中,而無需編寫任何 C 程式碼。這種方法將 Go 的效能和並發性與 Java 結合起來,簡化了整合並加快了開發速度。
關鍵因素包括確保 Java 和 Go 資料類型之間的兼容性、正確管理記憶體以避免洩漏以及設定強大的錯誤處理。透過將Java的生態系統與Go的效能最佳化相結合,開發人員可以創建高效且強大的應用程式。
與 JNI 相比,JNA 具有多種優勢,例如消除對 C 程式碼的需求、支援跨平台開發以及簡化本機整合流程以加快實施速度。
總之,使用 JNA 將 Go 函數整合到 Java 中是一種有效且直接的方法,可以在簡化開發的同時提高效能。
與往常一樣,程式碼片段可以在 GitHub 上取得。