The general procedure for calling Go in Java is as follows

1
go --> cgo --> jna --> java

There are two main problems to be solved in the whole process.

  1. how to transform data types in both languages
  2. when to clean up the useless data

The following is the process around the above call to elaborate, this article involves the full version of the code can be found in the following link:.

Go -> Cgo

This is the first step in cross-language calling, mainly with the help of cgo, which compiles Go code into C shared libraries. cgo is a tool for Go to provide interoperability with C. It provides a pseudo package called C for Go to access variables and functions in C, such as C.size_t, C.stdout, etc. It also provides 5 special functions for type conversion between the two languages.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Go string to C string
// The C string is allocated in the C heap using malloc.
// It is the caller's responsibility to arrange for it to be
// freed, such as by calling C.free (be sure to include stdlib.h
// if C.free is needed).
func C.CString(string) *C.char

// Go []byte slice to C array
// The C array is allocated in the C heap using malloc.
// It is the caller's responsibility to arrange for it to be
// freed, such as by calling C.free (be sure to include stdlib.h
// if C.free is needed).
func C.CBytes([]byte) unsafe.Pointer

// C string to Go string
func C.GoString(*C.char) string

// C data with explicit length to Go string
func C.GoStringN(*C.char, C.int) string

// C data with explicit length to Go []byte
func C.GoBytes(unsafe.Pointer, C.int) []byte

Note that functions in cgo cannot directly return slice/map data types with a go pointer (as opposed to a C pointer, which is managed by the go runtime), otherwise the following panic message will be reported.

1
panic: runtime error: cgo result has Go pointer

The reason is also very simple, go has gc, if allowed to return data with go pointer, then the data obtained in C code can not guarantee the legality, it is likely to have been gc, that is, the hanging pointer problem. The solution is simple: use the special conversion functions provided by go to convert the data to unsafe.Pointer and use it in C as void *.

As you can imagine, these special conversion functions must make a deep copy of the data to ensure its legality, see C.CBytes definition

1
2
3
4
5
6
7
8
const cBytesDef = `
func _Cfunc_CBytes(b []byte) unsafe.Pointer {
    p := _cgo_cmalloc(uint64(len(b)))
    pp := (*[1<<30]byte)(p)
    copy(pp[:], b)
    return p
}
`

But it also means that the Go/C code is responsible for freeing the useless data (which side does the freeing depends on the actual situation). Example.

1
2
3
4
5
func main() {
    cs := C.CString("Hello from stdio")
    C.myprint(cs)
    C.free(unsafe.Pointer(cs))
}

To export Go functions for C, you need to mark the functions with //export and the Go file needs to be under package main. Then you can use a build command like the one below to get a dynamic library that interoperates with C and produces a header file with the relevant signatures of the export functions.

1
2
# linux 下可输出到 libawesome.so,这里以 Mac 下的动态库为例
go build -v -o libawesome.dylib -buildmode=c-shared ./main.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//export Hello
func Hello(msg string) *C.char {
    return C.CString("hello " + strings.ToUpper(msg))

}

// 头文件中 Hello 的定义
// ptrdiff_t is the signed integer type of the result of subtracting two pointers.
// n 这里表示字符串的长度
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
extern char* Hello(GoString p0);

The complete code can be found in main.go, the corresponding header file libawesome.h.

Cgo -> JNA

This step is mainly about how to call C code in Java, there are two main ways, the

  • JNA, the advantage is easy to call, only need to write Java code, JNA framework is responsible for data type conversion in C/Java
  • JNI, the advantage is good performance, the disadvantage is cumbersome to call

Without going into the details of the differences, interested readers can refer to the following article.

JNA -> Java

This step focuses on how to call the libraries provided by the JNA framework for cross-language calls in Java code, and is the focus of this article. JNA maps Java basic types directly to C types of the same size, here excerpt is as follows

Native Type Size Java Type Common Windows Types
char 8-bit integer byte BYTE, TCHAR
short 16-bit integer short WORD
wchar_t 16/32-bit character char TCHAR
int 32-bit integer int DWORD
int boolean value boolean BOOL
long 32/64-bit integer NativeLong LONG
long long 64-bit integer long __int64
float 32-bit FP float
double 64-bit FP double
char* C string String LPCSTR
void* pointer Pointer LPVOID, HANDLE, LPXXX

For struct/pointer in C, JNA also provides Structure/Pointer class to correspond to it. JNA’s specific usage procedure can be found at

The third way of loading dynamic libraries in GettingStarted above (i.e. in the {OS}-{ARCH}/{LIBRARY} directory under resources) can package dynamic libraries into jars together, which is convenient for providing base libraries without additional configuration by the user.

1
2
3
4
5
resources/
├── darwin
   └── libawesome.dylib
├── linux-x86-64
   └── libawesome.so

vladimirvivien/go-cshared-examples This repository demonstrates JNA calls to four functions Add/Cosine/Sort/Log, but the return types of all four functions are basic types (int/float64) and there are no complex types such as string/slice, so here are five examples of complex type return problems.

  1. BadStringDemo.java This example demonstrates a common, but memory-leaking, way of returning a string on the web.
  2. GoodStringDemo.java This example demonstrates how to properly return the string
  3. AutoClosableStringDemo.java This example uses the AutoCloseable and try-with-resource features to free memory on top of GoodStringDemo
  4. ReturnByteSliceDemo.java This example demonstrates how to return a slice and how to handle multiple return values in Go in Java
  5. ReturnInterfaceDemo.java This example demonstrates the exception behavior when returning a structure with a Go Pointer

The above examples all use direct mapping to do JNA, readers can refer to vladimirvivien/go-cshared-examples to learn how to use interface mapping. Here is a simple performance test of the two mapping methods, and the stress test data is as follows.

Method input output which is better rate
Add two primitive ints int direct-mapping 1.38
Hello string FreeableString interface-mapping 1.169
Hello2 string Pointer direct-mapping 1.0083

The conclusion is

direct-mapping is better for basic types (including Pointer) and interface-mapping is slightly better for complex types.

The reason can be found at: https://stackoverflow.com/a/38081251

Summary

C, as a glue language to connect different high-level languages, does not have garbage collection, so developers should pay attention to recycling useless memory structures when doing JNA.