如何用java调用go?

1. 环境准备

A. GCC

控制台中输入

gcc -v

如果提示命令未找到,那么说明你的计算机中还没有gcc,去安装一个吧,gcc官方网站:https://gcc.gnu.org/ 如果从来没有安装过gcc的朋友可以直接安装win-build,可以帮你快速的安装 官方网站:http://mingw-w64.org/doku.php/download/win-builds

2. 编写go程序

我们这里只是编写一个简单的计算加法的程序,接受两个整数,然后计算他们的和,并返回。 在这里,我们将文件命名为libhello.go

package mainimport “C”//export Sumfunc Sum(a int, b int) int { return a + b}func main() {}

注意,即使是要编译成动态库,也要有main函数,上面的import “C”一定要有 而且一定要有注释

//export Sum

经测试,如果没有这个导出的DLL库中找不到对应的函数

3. 编译go程序

首先,将控制台的所在目录切换到go程序的所在目录,即libhello.go所在目录

A. Windows动态库

执行如下命令生成DLL动态链接库:

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

如果控制台没有报错,那么会在当前路径下生成libhello.dll文件

B. Linux/Unix/macOS动态库

执行如下命令生成SO动态库:

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

4. 在java中调用

A. JNA的引用

Java调用Native的动态库有两种方式,JNI和JNA,JNA是Oracle最新推出的与Native交互的方式,具体介绍我就不多说了,引用百度百科的连接:https://baike.baidu.com/item/JNA/8637274?fr=aladdin,有需要的朋友可以去看看。 在这里,我们使用JNA的方式,JNI的方式基本废弃,除非有特殊需要,在这里不多说,有需要可以联系我讨论。 新建Java工程,我使用的是Maven做包管理,所以直接引用JNA的依赖:

net.java.dev.jna jna 4.5.2

如果你没有使用包管理工具,可以直接下载Jar文件引入,下载地址也贴一下吧,也是4.5.2版本的: http://central.maven.org/maven2/net/java/dev/jna/jna/4.5.2/jna-4.5.2.jar

B. 创建接口

我们需要创建一个interface来映射DLL中的函数,之后我们可以通过interface的实例来访问DLL中的函数。

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是函数名,一定要与Go中事先写好的函数名保持一致 Native.loadLibrary()的第一个参数是一个字符串,要加载的动态库的名称或全路径,后面不需要加.dll或者.so的后缀。第二个参数为interface的类名称。

C. 调用

我们新建一个App类,作为main方法的入口类,在main方法中不需要多余的操作,只需要调用即可,在这里我们调用Sum方法,同时传如222 , 333,可以看到控制台输出:555

package cn.lemonit.robot.runner.executor;public class App { public static void main(String[] args) { System.out.println(LibHello.INSTANCE.Sum(222, 333)); }}

大功告成

5. 参数中包含字符串

A. 我真的大功告成了吗?

我们的程序总不能只传数值型的参数吧,我们把GO程序改一下,换成一个一字符串作为参数的函数,接受一个字符串参数,然后从控制台输出:hello: xxx,如下:

package mainimport “fmt”//export Hellofunc Hello(msg string) { fmt.Print(“hello: ” + msg)}func main() {}

按照上面2.B步骤中的写法,我们将java的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(String msg);}

接下来,我们调用这个接口,将0x02.C中的启动入口类App代码改成这样:

package cn.lemonit.robot.runner.executor;public class App { public static void main(String[] args) { LibHello.INSTANCE.Hello(“《Go Web编程实战派》”); }}

运行起来,但是报错了?

fatal error: string concatenation too longgoroutine 17 [running, locked to thread]:runtime.throw(0x644c1d4f, 0x1d) xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (runtime报错,没有意义,不贴了)

这是怎么回事,发现刚才在调用go build -buildmode=c-shared -o libhello.dll .libhello.go命令的时候在文件夹中除了libhello.dll被生成之外,还生成了一个libhello.h文件!!!这不是C的头文件么?文件的内容如下:

/* Created by “go tool cgo” – DO NOT EDIT. *//* package command-line-arguments */#line 1 “cgo-builtin-prolog”#include /* for ptrdiff_t below */#ifndef GO_CGO_EXPORT_PROLOGUE_H#define GO_CGO_EXPORT_PROLOGUE_Htypedef 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_Htypedef 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 __cplusplusextern “C” {#endifextern void Hello(GoString p0);#ifdef __cplusplus}#endif

这么大一篇子,往下翻翻翻,找到了我们的Hello函数的定义:

extern void Hello(GoString p0);

发现问题了,参数要的是GoString,而我们传的是Java的String,肯定类型不一致啊。那GoString是个什么东西呢,我该给他传什么?往上翻,找到了这么两行代码:

typedef struct { const char *p; ptrdiff_t n; } _GoString_;// …..typedef _GoString_ GoString;

嗯嗯嗯,看来这个GoString不过就是个C里面的结构体罢了,结构体里面一个char *一个ptrdiff_t,看来我们用java调用程序的时候,构造个这么样的结构体给他传进来应该就行了,好了,有思路了,开始折腾。

B. 创建GoString!

我们首先用JNA构建一个C的结构体类型,那么问题来了,JNA中char 可以直接用java的String来代替,那么ptrdiff_t这个玩意……有点无语,这是啥啊?经过一顿操作百度和谷歌,终于知道了,这个类型实际上是两个内存地址之间的距离的值,数据类型实际上就是C中的long int,在这里他表示的是字符串char 的长度,也就是字符串的长度呗~,知道这个就好办了,我们在Java中直接用long类型来代替它。 我们新建一个GoString类来对应C中的GoString结构体,也就是Go程序中的string,这块得说一下,有些人可能没有用过JNA,在JNA中若想定义一个结构体,需要创建一个类继承自com.sun.jna.Structure,熟悉C的人应该知道(不知道也没关系),向C中传值通常有两种,一种是传引用(就是传指针类型),一种是传真实值,在JNA里面做的话我们通常在这个结构体类中创建两个静态的内部类,这两个内部类继承自这个结构体类,并实现Structure.ByValue和Structure.ByReference接口,其中ByValue就是传真实值时候用的,ByReference就是传引用的时候用的,综上所述,我们的GoString类就应该长成这个样子:

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 getFieldOrder() { List 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); } }}

可以发现,我们重写了一个getFieldOrder方法,在里面新建一个list,然后把两个属性名作为字符串放到里面,然后当做返回值返回了。这个操作实际是为了告诉JNA,我这两个变量和C结构体中的变量是怎么个对应关系的,我们再来回顾一下刚才libhello.h中定义的GoString结构体(其实是省着你再往上翻看,费劲,直接粘出来方便你看):

typedef struct { const char *p; ptrdiff_t n; } _GoString_;

我们的字符串叫str,而char *的名称是p,我们的字符串长度叫length,而结构体中叫n,JNA又不是人工智能框架,肯定猜不出来你想把str对应到p,length想对应到n,所以我们在这里通过list的形式把字段名在list中排一个顺序,告诉JNA,我的str想对应结构体的第一个属性,length想对应结构体的第二个属性。(你可以试试,让fields.add的顺序调换一下,肯定会出问题)。

C. 有了GoString!

GoString有了,万事俱备,只欠东风了!用一把,我们把刚才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(“《Go Web编程实战派》”)); }}

运行!控制台成功输出:

hello: 《Go Web编程实战派》

成功了

6. 返回值中包含字符串

A. 做一个小实验~

我们把5中的Go函数Hello改一下,让结果通过返回值返回,而不是直接在控制台打印,变成这样滴:

package mainimport “C”//export Hellofunc 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);}

运行入口类App也对应修改一下:

package cn.lemonit.robot.runner.executor;public class App { public static void main(String[] args) { System.out.println(LibHello.INSTANCE.Hello(new GoString.ByValue(“《Go Web编程实战派》”)).str); }}

大功告成,运行一下!

panic: runtime error: cgo result has Go pointergoroutine 17 [running, locked to thread]:main._cgoexpwrap_b02601c1465e_Hello.func1(0xc04203deb8) _cgo_gotypes.go:59 +0x6cmain._cgoexpwrap_b02601c1465e_Hello(0xbe3ce0, 0xa, 0xc042008050, 0x10) _cgo_gotypes.go:61 +0xa1

《Go Web编程实战派》呢?在控制台中并没有找到啊。代码继续修改如下:

package mainimport “C”//export Hellofunc 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也得对应修改:

package cn.lemonit.robot.runner.executor;public class App { public static void main(String[] args) { System.out.println(LibHello.INSTANCE.Hello(new GoString.ByValue(“《Go Web编程实战派》”))); }}

好了,运行:

hello : 《Go Web编程实战派》

终于输出出来了!大功告成

郑重声明:本文内容及图片均整理自互联网,不代表本站立场,版权归原作者所有,如有侵权请联系管理员(admin#wlmqw.com)删除。
上一篇 2022年6月19日 21:11
下一篇 2022年6月19日 21:11

相关推荐

联系我们

联系邮箱:admin#wlmqw.com
工作时间:周一至周五,10:30-18:30,节假日休息