In Go language, there are no more than two main scenarios to use CGO: one is that Go generates dynamic library SO files (or static files) for other languages such as C to use; The other is to use C to encapsulate dynamic libraries for Go calls. To put it bluntly, who is responsible for the production of SO files and who uses them.

First, let’s take a look at how Go generates the c-shared class dynamic library for use by other languages. The whole process can be roughly divided into three steps:

  1. Develop interface functions;
  2. Build so file;
  3. Access to use.

Before starting, we need to do some preliminary preparation. Here we design a simple Go tool function library project: crypto. The project directory is as follows:

Crypto / ├─ go. Mod ├─ main.goCopy the code

The project is simple, so the interface functions are defined directly in the main.go file. There are no special requirements for the file name, but note that there are some basic requirements for a project to build the so library:

  1. The entire export project must have onemainpackageThe package;
  2. Since we need to havemainBag, then you have to have onemainThe entry function, even if it’s an empty function;
  3. If you want to activatecgoAnd you need to haveimport "C"Statement, otherwise the corresponding will not be generated.hHeader files.

Therefore, the final main.go minimum code structure is as follows:

// The following three items are essential
package main

import "C"

func main(a) {}
Copy the code

1. Develop interface functions

Interface functions are functions that you want to call. If you want to use a function defined by Go in another language, you need to mark the function with the //export annotation.

package main

import "C"

func main(a) {}

//export encode_data
func encode_data(data *C.char) *C.char {
   // Logical code
}
Copy the code

Here we define an encode_data interface function, but there are a few more details to note:

Note point 1:

// The name after export must be the same as the function name on the next line, but there is no requirement for the function name to be hump or snake, and it is not required to start with a capital letter. The resulting header function is as follows:

extern char* encode_data(char* data);
Copy the code

Note 2:

In addition, we should pay attention to the data types of function parameters and return parameters. The data types of Go and C are not the same, especially Pointers, memory management are different, and cannot be directly used. Therefore, the interface Go function cannot directly return data of the Go pointer type, such as string

// Error
// String is a pointer to Go, and C cannot use it.
func encode_data(data *C.char) string {}
Copy the code

This code returns a Go string of type string containing a pointer to Go. Cgo result has Go Pointer error if called directly by C.

The correct approach is to use a wrapper function provided by CGO, such as c.string (), or to construct the pointer structure corresponding to C directly.

func encode_data(data *C.char) *C.char {
   return C.CString("c string") // Use C pointer data
}
Copy the code

Note 3:

But that’s not all!! While it’s easy to return a C-type String (or pointer to a character array) using the C. String function, this triggers another potential problem. Data constructed using c. String is in heap memory, not in static area. So it’s official that the data needs to be released manually:

var cmsg *C.char = C.CString("hi")
defer C.free(unsafe.Pointer(cmsg))
Copy the code

But in this scenario, we can’t release it immediately because we need to return it to the caller and they need to use!

func encode_data(data *C.char) *C.char {
    var cmsg *C.char = C.CString("c string") 
    //defer c.free (unsafe.pointer (CMSG)) <------- cannot be released, otherwise the caller will not get the data
   
   return cmsg
}
Copy the code

So it’s up to the caller to release the data, so you need to communicate with the caller to manually release the similar data, although it’s a little hard to understand that the string is in the heap.

int main(a) {
    char * encodeData = encode_data("test"); // The returned data is in the heap, not in the static area
    
    // Remember to release
    free(encodeData);
}
Copy the code

2. Build the so file

Once you’ve designed the function interface, it’s easy to just use the Go Build tool.

#Build the dynamic library file
go build -o libcrypto.so -buildmode=c-shared main.go

#Build a static library file
go build -o libcrypto.a -buildmode=c-archive main.go
Copy the code

It is recommended that output file commands be prefixed with lib*, which is a common Linux standard. Go Build generates the corresponding header and library files:

Libcrypto. h libcrypto.so # or libcryptoCopy the code

In addition, if your export function design is not ready, and you want to export the. H header file, also no problem, can directly use:

go tool cgo -exportheader libcrypto.h main.go
Copy the code

Go Tool CGO and GO Build are available in the Help manual for more details about cGO operation parameters.

3. Access and use

With.h files and.so libraries, access is not complicated, not only C language, lua, C ++ actually can use. Just use the functions defined in this header file in your project and either configure the path to the libcrypto. So library during connection build and run, or register it directly to the system path.

#include <stdio.h>
#include "libs/libcrypto.h"
#include <stdlib.h>

int main(a) {
    char * encoded = encode_data("hi cgo");
    printf("code: %s \n", encoded);
    
    // Pay attention to memory release
    free(encoded);

    return 0;
}

Copy the code

Compile operation

#compile
gcc -L./libs -lcrypto main.c -o app

#run
LD_LIBRARY_PATH=./libs/:${LD_LIBRARY_PATH} ./app
Copy the code

Transmission result:

c string
Copy the code

The macOS dynamic library variable is DYLD_LIBRARY_PATH.

The outside

In the function interface design section, we emphasized that c. String String data is in the heap, not in the static area, so we need to manually execute the free function to manually reclaim memory. Here we use another cool memory overflow detection tool to verify: valgrind.

The introduction and installation of the tool can be found at Google

LD_LIBRARY_PATH=./libs valgrind --leak-check=full ./app
Copy the code

The output

==29641== HEAP SUMMARY: ==29641== in use at exit: 3,465 bytes in 7 blocks ==29641== Total HEAP Usage: 13 allocs, 6 frees, 3,641 bytes ==29641== ==29641== 9 bytes in 1 blocks are definitely lost in loss record 1 of 6 ==29641== at 0x4C29E03: malloc (vg_replace_malloc.c:309) ==29641== by 0x4ECE904: _cgo_50f941cac2e5_Cfunc__Cmalloc (_cgo_export.c:47) ==29641== by 0x4EC7C07: The runtime. Asmcgocall (/ usr/local/go1.15 / SRC/runtime/asm_amd64. S: 656) = = 29641 = = by 0 x4ea0249: Runtime. Newextram (/ usr/local/go1.15 / SRC/runtime/proc. Go: 1607) = = 29641 = = by 0 xc0000005ff:????? ==29641== by 0x4E9F41F: ??? (in /home/liangqi1/crypto/libs/libcrypto.so) ==29641== by 0x1FFF00016F: ??? . . ==29641== LEAK SUMMARY: ==29641== definitely lost: 9 bytes in 1 blocks ==29641== indirectly lost: 0 bytes in 0 blocks ==29641== Possibly lost: 3,456 bytes in 6 blocks ==29641== still reachable: 0 bytes in 0 blocks ==29641== suppressed: 0 bytes in 0 blocksCopy the code

Definitely lost means an absolute memory overflow, with 9 bytes of data spilled, which is exactly what the demo function returns: C string plus a c terminator \0, which is exactly 9 bytes.

Since ValGrind is also unable to trace the dynamic inner function stack of.so, it cannot locate the specific line of code. Said. However, you can compare this by opening or commenting out the free() function code.