参考文档:
1. 基本原理
Java Native Access(JNA)提供一组Java工具类用于在运行期间动态访问系统本地库(Native library:如 windows的 dll文件、linux的so文件),而不需要编写任何Native/ JNI 代码。开发人员只要在一个java接口中描述目标 native library 的函数与结构,JNA 将自动实现 Java 接口到 native function的 映射。
java type 和 native type 在进行转换的时候,是有一个对应的映射关系的,根据JNA的官方文档(),以下在表格中列出了部分 type-mapping的关系,这些关系也将会在后面的case中进行测试:
NativeMappedNativeLongPointerjna.encodingStringor explicitlyor explicitly
然后基于go语言中的cgo工具,可以将 C type 转化为 Go Type
2. 编写Go函数
以下编写6个case进行测试,前3个case是基本类型(int、double、boolean)在java与go之间的传递,后3个case都是string类型的传递。
package main
import "C"
import (
"fmt"
"math"
"unsafe"
)
// java:int --> C:int == C.int --> Go:int
//export Add
func Add(a, b C.int) C.int {
aGo := int(a)
bGo := int(b)
res := aGo + bGo
return C.int(res)
}
// java:double --> C:double == C.double --> Go:float64
//export Cosine
func Cosine(x C.double) C.double {
xGo := float64(x)
res := math.Cos(xGo)
return C.double(res)
}
//
//export Hello1
func Hello1(world string, test string) *C.char {
res := "Hello1," + world
fmt.Println(test)
return C.CString(res)
}
//export Hello2
func Hello2(raw *C.char, size C.int) *C.char {
data := C.GoBytes(unsafe.Pointer(raw), size)
dataStr := "Hello2," + string(data)
return C.CString(dataStr)
}
//export Hello3
func Hello3(test string, raw *C.char, size C.int) *C.char {
res := "Hello3," + test
data := C.GoBytes(unsafe.Pointer(raw), size)
dataStr := string(data)
fmt.Println("Hello3," + dataStr)
return C.CString(res)
}
func main(){}
关于java中string的传递,可以通过两种方式:
第一种方式:
java中的string,可以编码成为 byte[ ] 数组,根据JNA官方的Type Mapping所述,会映射成为 C中的 一个指针,该指针指向一个 C.char 类型的数组。最后可以借助Cgo,将其转化为go中的 [ ]byte,最后就可以轻易的转化为go中的string。
第二种方式:
把go程序编译成为 native library文件后,会在目录下生成一个 xxx.h 的C 语言头文件:
我们可以看到go中的 string,也就是C中的一个 struct。因此我们可以在java代码中构造一个java的Struct来对应C中的struct。
以此为思路,参见JNA官方文档的 Type Mapping:Structs:
Structurestructstruct *Structure.ByValueStructure.ByReference
Structure
/* Code generated by cmd/cgo; DO NOT EDIT. */
/* package command-line-arguments */
#line 1 "cgo-builtin-export-prolog"
#include <stddef.h> /* for ptrdiff_t below */
#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
#endif
#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];
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif
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
// java:int --> C:int == C.int --> Go:int
extern int Add(int p0, int p1);
// java:double --> C:double == C.double --> Go:float64
extern double Cosine(double p0);
// java:boolean --> C:int == C.int --> Go:int
extern int Boolean(int p0, int p1);
extern char* Hello1(GoString p0, GoString p1);
extern char* Hello2(char* p0, int p1);
extern char* Hello3(GoString p0, char* p1, int p2);
#ifdef __cplusplus
}
#endif
3. 编译go程序
将go程序编译成为 native library文件。
如果是windows系统,则编译命令为:
go build -buildmode=c-shared -o test.dll .\test1.go
如果是linux系统,则编译命令为:
go build -buildmode=c-shared -o libtest.so .\test1.go
4. 动态库在java中的映射
首先,我们需要创建一个java中的interface来映射native library,之后我们就可以通过该 interface 的实例来访问native library中的一些函数。
然后为了能映射C中的struct,我们还需要编写一个 java的struct - GoString
实例化interface时,引用 .dll文件的 路径 可以设为绝对路径。
import com.sun.jna.*;
import com.sun.jna.Native;
import java.util.Arrays;
import java.util.List;
import java.lang.Math;
public class Client {
public interface Awesome extends Library {
// GoString class maps to:
// C type struct { const char *p; GoInt n; }
public class GoString extends Structure {
public static class ByValue extends GoString implements Structure.ByValue {}
public String p;
public long n;
protected List getFieldOrder(){
return Arrays.asList(new String[]{"p","n"});
}
}
int Add(int a, int b);
double Cosine(double x);
boolean Boolean(boolean t, boolean f);
String Hello1(GoString.ByValue world, GoString.ByValue test);
String Hello2(byte[] raw, int len);
String Hello3(GoString.ByValue world, byte[] raw, int len);
}
public static void main(String[] args) {
Awesome awesome = Native.loadLibrary("{dir_path}\\test.dll", Awesome.class);
// {dir_path}\test.dll
// {dir_path}/libtest.so
System.out.println("==========================case1: Add==========================");
System.out.println(awesome.Add(5, 2)); // case1: Add
System.out.println("==========================case2: Cosine==========================");
System.out.println(awesome.Cosine(Math.PI/3)); // case2: Cosine
System.out.println("==========================case3: Boolean==========================");
System.out.println(awesome.Boolean(true, false)); // case3: Boolean
System.out.println("==========================case4: Hello1==========================");
// case4: Hello1
Awesome.GoString.ByValue str1 = new Awesome.GoString.ByValue();
str1.p = "world1";
str1.n = str1.p.length();
Awesome.GoString.ByValue test = new Awesome.GoString.ByValue();
test.p = "test";
test.n = test.p.length();
System.out.println(awesome.Hello1(str1, test));
// case5: Hello2
System.out.println("==========================case5: Hello2==========================");
String str = "world2";
byte raw[] = str.getBytes();
System.out.println(awesome.Hello2(raw, raw.length));
// case6: Hello3
System.out.println("==========================case6: Hello3==========================");
Awesome.GoString.ByValue str3 = new Awesome.GoString.ByValue();
str3.p = "test";
str3.n = str3.p.length();
String str4 = "world3";
byte raw3[] = str4.getBytes();
System.out.println(awesome.Hello3(str3, raw3, raw3.length));
}
}
输出结果:
==========================case1: Add==========================
7
==========================case2: Cosine==========================
0.5000000000000001
==========================case3: Boolean==========================
java:true --> go: -1
java:false --> go: 0
true
==========================case4: Hello1==========================
test Hello1,world1
==========================case5: Hello2==========================
Hello2,world2
==========================case6: Hello3==========================
Hello3,world3 test