1. 環(huán)境準(zhǔn)備
A. GCC
在控制臺(tái)中輸入
gcc -v
如果提示命令未找到螟加,那么說明你的計(jì)算機(jī)中還沒有g(shù)cc,去安裝一個(gè)吧,gcc官方網(wǎng)站:https://gcc.gnu.org/ 如果從來沒有安裝過gcc的朋友可以直接安裝win-build,可以幫你快速的安裝 官方網(wǎng)站:http://mingw-w64.org/doku.php/download/win-builds
2. 編寫go程序
我們這里只是編寫一個(gè)簡單的計(jì)算加法的程序规哪,接受兩個(gè)整數(shù),然后計(jì)算他們的和掠械,并返回。 在這里注祖,我們將文件命名為libhello.go
package main
import "C"
//export Sum
func Sum(a int, b int) int {
return a + b
}
func main() {
}
注意猾蒂,即使是要編譯成動(dòng)態(tài)庫,也要有main函數(shù)是晨,上面的import "C"
一定要有 而且一定要有注釋
//export Sum
經(jīng)測(cè)試肚菠,如果沒有這個(gè)導(dǎo)出的DLL庫中找不到對(duì)應(yīng)的函數(shù)
3. 編譯go程序
首先,將控制臺(tái)的所在目錄切換到go程序的所在目錄罩缴,即libhello.go所在目錄
A. Windows動(dòng)態(tài)庫
執(zhí)行如下命令生成DLL動(dòng)態(tài)鏈接庫:
go build -buildmode=c-shared -o libhello.dll .\libhello.go
如果控制臺(tái)沒有報(bào)錯(cuò),那么會(huì)在當(dāng)前路徑下生成libhello.dll文件
B. Linux/Unix/macOS動(dòng)態(tài)庫
執(zhí)行如下命令生成SO動(dòng)態(tài)庫:
go build -buildmode=c-shared -o libhello.so .\libhello.go
4. 在java中調(diào)用
A. JNA的引用
Java調(diào)用Native的動(dòng)態(tài)庫有兩種方式,JNI和JNA奏甫,JNA是Oracle最新推出的與Native交互的方式乍丈,具體介紹我就不多說了,引用百度百科的連接:https://baike.baidu.com/item/JNA/8637274?fr=aladdin乓土,有需要的朋友可以去看看。 在這里,我們使用JNA的方式戳表,JNI的方式基本廢棄,除非有特殊需要昼伴,在這里不多說匾旭,有需要可以聯(lián)系我討論。 新建Java工程圃郊,我使用的是Maven做包管理价涝,所以直接引用JNA的依賴:
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>4.5.2</version>
</dependency>
如果你沒有使用包管理工具,可以直接下載Jar文件引入持舆,下載地址也貼一下吧色瘩,也是4.5.2版本的: http://central.maven.org/maven2/net/java/dev/jna/jna/4.5.2/jna-4.5.2.jar
B. 創(chuàng)建接口
我們需要?jiǎng)?chuàng)建一個(gè)interface來映射DLL中的函數(shù),之后我們可以通過interface的實(shí)例來訪問DLL中的函數(shù)吏廉。
package cn.lemonit.robot.runner.executor;
import com.sun.jna.Library;
import com.sun.jna.Native;
public interface LibHello extends Library {
LibHello INSTANCE = (LibHello) Native.loadLibrary("E:/workspace/libhello", LibHello.class);
int Sum(int a, int b);
}
注意泞遗,Sum是函數(shù)名,一定要與Go中事先寫好的函數(shù)名保持一致 Native.loadLibrary()的第一個(gè)參數(shù)是一個(gè)字符串席覆,要加載的動(dòng)態(tài)庫的名稱或全路徑史辙,后面不需要加.dll或者.so的后綴。第二個(gè)參數(shù)為interface的類名稱佩伤。
C. 調(diào)用
我們新建一個(gè)App類聊倔,作為main方法的入口類,在main方法中不需要多余的操作生巡,只需要調(diào)用即可耙蔑,在這里我們調(diào)用Sum方法,同時(shí)傳如222 孤荣, 333甸陌,可以看到控制臺(tái)輸出:555
package cn.lemonit.robot.runner.executor;
public class App {
public static void main(String[] args) {
System.out.println(LibHello.INSTANCE.Sum(222, 333));
}
}
大功告成,我終于玩通了Java調(diào)用Go程序Q喂伞G怼!疯汁! 牲尺???不對(duì)勁谤碳,有點(diǎn)太過于幸災(zāi)樂禍了溃卡,往下繼續(xù)
5. 參數(shù)中包含字符串
A. 我真的大功告成了嗎?
我們的程序總不能只傳數(shù)值型的參數(shù)吧蜒简,我們把GO程序改一下瘸羡,換成一個(gè)一字符串作為參數(shù)的函數(shù),接受一個(gè)字符串參數(shù)臭蚁,然后從控制臺(tái)輸出:hello: xxx最铁,如下:
package main
import "fmt"
//export Hello
func Hello(msg string) {
fmt.Print("hello: " + msg)
}
func main() {
}
按照上面2.B步驟中的寫法,我們將java的LibHello接口改成這個(gè)樣子:
package cn.lemonit.robot.runner.executor;
import com.sun.jna.Library;
import com.sun.jna.Native;
public interface LibHello extends Library {
LibHello INSTANCE = (LibHello) Native.loadLibrary("E:/workspace/libhello", LibHello.class);
void Hello(String msg);
}
接下來垮兑,我們調(diào)用這個(gè)接口冷尉,將 2.C 中的啟動(dòng)入口類App代碼改成這樣:
package cn.lemonit.robot.runner.executor;
public class App {
public static void main(String[] args) {
LibHello.INSTANCE.Hello("LemonIT.CN");
}
}
運(yùn)行起來,咦系枪?報(bào)錯(cuò)了雀哨??私爷?
fatal error: string concatenation too long
goroutine 17 [running, locked to thread]:
runtime.throw(0x644c1d4f, 0x1d)
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (runtime報(bào)錯(cuò)雾棺,沒有意義,不貼了)
這是怎么回事衬浑,我傳的是一個(gè)很標(biāo)準(zhǔn)的String啊捌浩,怎么會(huì)報(bào)錯(cuò)呢? 在一陣無頭緒中工秩,發(fā)現(xiàn)剛才在調(diào)用go build -buildmode=c-shared -o libhello.dll .\libhello.go
命令的時(shí)候在文件夾中除了libhello.dll被生成之外尸饺,還生成了一個(gè)libhello.h文件!V摇浪听!這不是C的頭文件么?出于好奇眉菱,打開看看有什么高大上的東西迹栓,這一打開還真是嚇到我了:
/* Created by "go tool cgo" - DO NOT EDIT. */
/* package command-line-arguments */
#line 1 "cgo-builtin-prolog"
#include <stddef.h> /* for ptrdiff_t below */
#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
#endif
/* Start of preamble from import "C" comments. */
/* End of preamble from import "C" comments. */
/* Start of boilerplate cgo prologue. */
#line 1 "cgo-gcc-export-header-prolog"
#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H
typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef __SIZE_TYPE__ GoUintptr;
typedef float GoFloat32;
typedef double GoFloat64;
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;
/*
static assertion to make sure the file is being used on architecture
at least with matching size of GoInt.
*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
typedef _GoString_ GoString;
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
#endif
/* End of boilerplate cgo prologue. */
#ifdef __cplusplus
extern "C" {
#endif
extern void Hello(GoString p0);
#ifdef __cplusplus
}
#endif
這么大一篇子,往下翻翻翻俭缓,找到了我們的Hello函數(shù)的定義:
extern void Hello(GoString p0);
發(fā)現(xiàn)問題了克伊,人家參數(shù)要的事GoString,而我們傳的是Java的String华坦,肯定類型不一致啊愿吹。那GoString是個(gè)什么東西呢,我該給他傳什么季春?往上翻洗搂,找到了這么兩行代碼:
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
// .....
typedef _GoString_ GoString;
嗯嗯嗯消返,看來這個(gè)GoString不過就是個(gè)C里面的結(jié)構(gòu)體罷了载弄,結(jié)構(gòu)體里面一個(gè)char *一個(gè)ptrdiff_t耘拇,看來我們用java調(diào)用程序的時(shí)候,構(gòu)造個(gè)這么樣的結(jié)構(gòu)體給他傳進(jìn)來應(yīng)該就行了宇攻,好了惫叛,有思路了,開始折騰逞刷。
B. 創(chuàng)建GoString嘉涌!
我們首先用JNA構(gòu)建一個(gè)C的結(jié)構(gòu)體類型,那么問題來了夸浅,JNA中char *可以直接用java的String來代替仑最,那么ptrdiff_t這個(gè)玩意……有點(diǎn)無語,這是啥胺警医?經(jīng)過一頓操作百度和谷歌,終于知道了坯钦,這個(gè)類型實(shí)際上是兩個(gè)內(nèi)存地址之間的距離的值预皇,數(shù)據(jù)類型實(shí)際上就是C中的long int
,在這里他表示的是字符串char *的長度婉刀,也就是字符串的長度唄~吟温,知道這個(gè)就好辦了,我們?cè)贘ava中直接用long類型來代替它突颊。 我們新建一個(gè)GoString類來對(duì)應(yīng)C中的GoString結(jié)構(gòu)體鲁豪,也就是Go程序中的string,這塊得說一下洋丐,有些人可能沒有用過JNA呈昔,在JNA中若想定義一個(gè)結(jié)構(gòu)體,需要?jiǎng)?chuàng)建一個(gè)類繼承自com.sun.jna.Structure
友绝,熟悉C的人應(yīng)該知道(不知道也沒關(guān)系)堤尾,向C中傳值通常有兩種,一種是傳引用(就是傳指針類型)迁客,一種是傳真實(shí)值郭宝,在JNA里面做的話我們通常在這個(gè)結(jié)構(gòu)體類中創(chuàng)建兩個(gè)靜態(tài)的內(nèi)部類,這兩個(gè)內(nèi)部類繼承自這個(gè)結(jié)構(gòu)體類掷漱,并實(shí)現(xiàn)Structure.ByValue和Structure.ByReference接口粘室,其中ByValue就是傳真實(shí)值時(shí)候用的,ByReference就是傳引用的時(shí)候用的卜范,綜上所述衔统,我們的GoString類就應(yīng)該長成這個(gè)樣子:
package cn.lemonit.robot.runner.executor;
import com.sun.jna.Structure;
import java.util.ArrayList;
import java.util.List;
public class GoString extends Structure {
public String str;
public long length;
public GoString() {
}
public GoString(String str) {
this.str = str;
this.length = str.length();
}
@Override
protected List<String> getFieldOrder() {
List<String> fields = new ArrayList<>();
fields.add("str");
fields.add("length");
return fields;
}
public static class ByValue extends GoString implements Structure.ByValue {
public ByValue() {
}
public ByValue(String str) {
super(str);
}
}
public static class ByReference extends GoString implements Structure.ByReference {
public ByReference() {
}
public ByReference(String str) {
super(str);
}
}
}
可以發(fā)現(xiàn),我們重寫了一個(gè)getFieldOrder方法,在里面新建一個(gè)list锦爵,然后把兩個(gè)屬性名作為字符串放到里面舱殿,然后當(dāng)做返回值返回了。這個(gè)操作實(shí)際是為了告訴JNA险掀,我這兩個(gè)變量和C結(jié)構(gòu)體中的變量是怎么個(gè)對(duì)應(yīng)關(guān)系的沪袭,我們?cè)賮砘仡櫼幌聞偛舕ibhello.h中定義的GoString結(jié)構(gòu)體(其實(shí)是省著你再往上翻看,費(fèi)勁樟氢,直接粘出來方便你看):
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
我們的字符串叫str冈绊,而char *的名稱是p,我們的字符串長度叫l(wèi)ength埠啃,而結(jié)構(gòu)體中叫n死宣,JNA又不是人工智能框架,肯定猜不出來你想把str對(duì)應(yīng)到p碴开,length想對(duì)應(yīng)到n十电,所以我們?cè)谶@里通過list的形式把字段名在list中排一個(gè)順序,告訴JNA叹螟,我的str想對(duì)應(yīng)結(jié)構(gòu)體的第一個(gè)屬性鹃骂,length想對(duì)應(yīng)結(jié)構(gòu)體的第二個(gè)屬性。(你可以試試罢绽,讓fields.add的順序調(diào)換一下畏线,肯定會(huì)出問題)。
C. 有了GoString良价!我又可以幸災(zāi)樂禍了G夼埂?明垢?蚣常?
好了,GoString有了痊银,萬事俱備抵蚊,只欠東風(fēng)了!用一把溯革,我們把剛才0x05.A中的LibHello類改成這樣:
package cn.lemonit.robot.runner.executor;
import com.sun.jna.Library;
import com.sun.jna.Native;
public interface LibHello extends Library {
LibHello INSTANCE = (LibHello) Native.loadLibrary("E:/workspace/libhello", LibHello.class);
void Hello(GoString.ByValue msg);
}
App入口類代碼改成這樣:
package cn.lemonit.robot.runner.executor;
public class App {
public static void main(String[] args) {
LibHello.INSTANCE.Hello(new GoString.ByValue("LemonIT.CN"));
}
}
運(yùn)行贞绳!控制臺(tái)成功輸出:
hello: LemonIT.CN
哈哈哈!成功了致稀,有點(diǎn)小激動(dòng)冈闭!把代碼發(fā)給朋友們看!6兜ァ萎攒!有一個(gè)朋友問我遇八,你這Hello函數(shù)的結(jié)果能不能不在Go中的控制臺(tái)打印,而是在Java中打印到控制臺(tái)耍休?額……我猶豫了一下押蚤,應(yīng)該能吧……!
6. 返回值中包含字符串
A. 做一個(gè)小實(shí)驗(yàn)~
我們把5中的Go函數(shù)Hello改一下羹应,讓結(jié)果通過返回值返回,而不是直接在控制臺(tái)打印次屠,變成這樣滴:
package main
import "C"
//export Hello
func Hello(msg string) string{
return "hello:" + msg
}
func main() {
}
既然返回值也是string园匹,那JNA這邊也得小改一波,把0x05.C中的LibHello類改成這樣:
package cn.lemonit.robot.runner.executor;
import com.sun.jna.Library;
import com.sun.jna.Native;
public interface LibHello extends Library {
LibHello INSTANCE = (LibHello) Native.loadLibrary("E:/workspace/libhello", LibHello.class);
GoString.ByValue Hello(GoString.ByValue msg);
}
運(yùn)行入口類App也對(duì)應(yīng)修改一下:
package cn.lemonit.robot.runner.executor;
public class App {
public static void main(String[] args) {
System.out.println(LibHello.INSTANCE.Hello(new GoString.ByValue("LemonIT.CN")).str);
}
}
大功告成劫灶,運(yùn)行一下裸违!
panic: runtime error: cgo result has Go pointer
goroutine 17 [running, locked to thread]:
main._cgoexpwrap_b02601c1465e_Hello.func1(0xc04203deb8)
_cgo_gotypes.go:59 +0x6c
main._cgoexpwrap_b02601c1465e_Hello(0xbe3ce0, 0xa, 0xc042008050, 0x10)
_cgo_gotypes.go:61 +0xa1
嗯?這啥本昏?我的LemonIT.CN呢供汛?在控制臺(tái)中并沒有找到啊涌穆!有點(diǎn)讓人發(fā)狂怔昨,怎么一步一個(gè)坎~不過想想比爾蓋茨,我還是決定做一名脾氣好的程序員宿稀,慢慢研究吧趁舀。
B. 事情總有解決辦法!(車到山前必有路祝沸,有路必有豐田車)
雖然沒有LemonIT.CN矮烹,但是看控制臺(tái)中的error,cgo result has Go pointer罩锐,還是找到了一絲線索奉狈。又開始一頓操作百度和谷歌。原來涩惑,Go有自己的GC(垃圾回收仁期,不解釋),通俗點(diǎn)說就是我Go語言的指針你們其他語言別想用竭恬!額蟀拷,那咋整!急的我連大學(xué)時(shí)候的課堂筆記都翻出來了萍聊。無意中看到了當(dāng)時(shí)寫的借助JNA與C通信问芬,C中將char *返回給Java,然后Java使用String即可接收寿桨。嗯此衅,嗯强戴?這條咋忘了呢?哈哈哈挡鞍,豈不是我把Go中的string轉(zhuǎn)成C的char *返回就可以了骑歹?好了,讓我們?cè)嚿夏敲匆辉嚹ⅲ褎偛诺腉o中的Hello函數(shù)再次修改一波:
package main
import "C"
//export Hello
func Hello(msg string) *C.char{
return C.CString("hello : " + msg)
}
func main() {
}
同樣滴道媚,我們的JNA這邊也得改一改,把LibHello類修改成這樣:
package cn.lemonit.robot.runner.executor;
import com.sun.jna.Library;
import com.sun.jna.Native;
public interface LibHello extends Library {
LibHello INSTANCE = (LibHello) Native.loadLibrary("E:/workspace/libhello", LibHello.class);
String Hello(GoString.ByValue msg);
}
LibHello既然改了翘县,那么入口類App也得對(duì)應(yīng)修改:
package cn.lemonit.robot.runner.executor;
public class App {
public static void main(String[] args) {
System.out.println(LibHello.INSTANCE.Hello(new GoString.ByValue("LemonIT.CN")));
}
}
好了好了好了最域,運(yùn)行:
hello : LemonIT.CN
終于輸出出來了!
7. 總結(jié)
這個(gè)Go和Java的交互剛剛走了這一小小步就一步一個(gè)坎锈麸,看來真不能隨便的幸災(zāi)樂禍岸浦!忘伞!還得謙虛薄翅,路才能越走越遠(yuǎn)。雖然費(fèi)了這么大勁就解決了這么點(diǎn)小事氓奈,但是Go語言的優(yōu)勢(shì)是很大的翘魄,還是很值得我來折騰的,相信能讀到這里的朋友也是對(duì)Go語言非常的喜愛舀奶,大家一起加油吧熟丸,歡迎各位大佬來指正批評(píng)~
感謝作者:LemonITCN
原文鏈接:https://studygolang.com/articles/13646#reply1