golang calls the c library, cgo (1)

Basic usage

Sometimes we need to use golang to call some C class libraries, because it is time-consuming to use golang to implement it repeatedly, and it is better to call some mature functions directly. Of course, the premise is to install the c library first. CGO can directly use C code, or C static library, or dynamic library, of course, C ++ is also possible.

The CGO feature in golang can create Go packages that call C code.

package main

import "C"

func main() {<!-- -->

}

Then when compiling, you need to specify CGO_ENABLED=1, of course, the default value is 1, so you don’t need to specify it.

> go env | grep CGO
GCCGO="gccgo"
CGO_ENABLED="1"
CGO_CFLAGS="-O2 -g"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-O2 -g"
CGO_FFLAGS="-O2 -g"
CGO_LDFLAGS="-O2 -g"

The default C compiler is gcc.

Once closed it will affect CGO compilation. Special attention needs to be paid, CGO will be turned off by default when cross-compiling.

package main

/*

#include <stdio.h>
#include <stdlib.h>

*/
import "C"

func main() {<!-- -->
s := C.CString("hello world.")
C. puts(s)
    C. free(unsafe. Pointer(s))
}
> go run main.go
hello world.

Note that the comment should be next to import "C", otherwise it is an ordinary comment, not an instruction.

C.puts(s) is to call the puts() function in stdio.h, but you need to convert the go string to c first String, so C.CString() is a function provided by the pseudo-package C.

cgo puts the C language symbols referenced by the current package into the virtual C package. At the same time, other Go language packages that the current package depends on may also introduce similar virtual C packages through cgo, but the virtual C symbols introduced by different Go language packages Types between C packages cannot be used universally. This constraint may have a little impact on constructing some cgo auxiliary functions by yourself.

In the comment before the import "C" statement, you can use the #cgo statement to set the relevant parameters of the compilation phase and the linking phase. The parameters in the compilation phase are mainly used to define related macros and specify the header file retrieval path. The parameters of the link stage are mainly to specify the library file retrieval path and the library file to be linked.

// #cgo CFLAGS: -DPNG_DEBUG=1 -I./include
// #cgo LDFLAGS: -L/usr/local/lib -lpng
// #include <png.h>
import "C"

Basic data types

The basic numerical type conversion comparison table between golang and C is as follows:

C language type CGO type Go language type
char C. char byte
singed char C.schar int8
unsigned char C.uchar uint8
short C.short int16
unsigned short C.ushort uint16
int C.int int32
unsigned int C.uint uint32
long C.long int32
unsigned long C.ulong uint32
long long int C.longlong int64
unsigned long long int C. ulonglong uint64
float C.float float32
double C.double float64
size_t C.size_t uint

Note that the integer in C such as int does not define a specific word length in the standard, but it is generally considered to be 4 bytes by default. The C.int in the corresponding CGO type clearly defines the word length as 4, but the int word length in golang It is 8, so the corresponding golang type is not int but int32. In order to avoid misuse, C code is best to use the C99 standard numeric type, and the corresponding conversion relationship is as follows:

C language type CGO type Go language type
int8_t C.int8_t int8
uint8_t C.uint8_t uint8
int16_t C.int16_t int16
uint16_t C.uint16_t uint16
int32_t C.int32_t int32
uint32_t C.uint32_t uint32
int64_t C.int64_t int64
uint64_t C.uint64_t uint64

C type is used in C code, CGO type and GO type are both used in GO code, CGO type is converted from GO type to C type in GO code and then passed to C function for use. Then the return value of the C function in the GO code is also of the CGO type.

package main

/*

#include <stdint.h>
static int32_t add(int32_t a, int32_t b) {
return a + b;
}

*/
import "C"
import "fmt"

func main() {<!-- -->
var a, b int32 = 1, 2
var c int32 = int32(C.add(C.int32_t(a), C.int32_t(b)))
fmt. Println(c)
}

string

The C language does not have a string type, and a char array is used instead, so CGO provides functions for converting Go and C type strings, all of which are implemented by copying data.

// 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 if the program is resident in memory, you need to use C.free(unsafe.Pointer(s)) to release the memory created by C.CString().

package main

/*

#include <stdio.h>
#include <stdlib.h>

static void SayHello(const char* s) {
puts(s);
}
*/
import "C"
import "unsafe"

func main() {<!-- -->
s := C.CString("hello world.")
C. Say Hello(s)
C. free(unsafe. Pointer(s))
}

We can also put the SayHello function into a C language source file in the current directory. Because it is written in a separate C file, in order to allow external references, the static modifier of the function needs to be removed, because the static function can only be seen in the file where it is declared , other files cannot reference the function. hello.c

#include <stdio.h>

void SayHello(const char* s) {<!-- -->
puts(s);
}

Then declare the SayHello function first in the CGO part

package main

/*
#include "hello.c"
*/
import "C"

func main() {<!-- -->
C.SayHello(C.CString("hello world."))
}

or modular

hello.h

void SayHello(const char* s);

hello.c

#include "hello.h"
#include <stdio.h>

void SayHello(const char* s) {<!-- -->
puts(s);
}

You can also use c++ to implement the hello.h interface

#include <iostream>

extern "C" {<!-- -->
#include "hello.h"
}

void SayHello(const char* s) {<!-- -->
std::cout << s;
}

or golang implementation

hello.h

void SayHello(char* s);

main.go

//export SayHello
func SayHello(s *C. char) {<!-- -->
fmt. Print(C. GoString(s))
}

CGO is not only used to call C language functions in Go language, but also can be used to export Go language functions to C language function calls. We export the function SayHello implemented in the Go language as a C language function through the //export SayHello command of CGO. In order to adapt to the C language function exported by CGO, we prohibit the const modifier in the declaration statement of the function.

or to simplify

package main

//void SayHello(char* s);
import "C"

import (
"fmt"
)

func main() {
C.SayHello(C.CString("Hello, World\\
"))
}

//export SayHello
func SayHello(s *C. char) {<!-- -->
fmt. Print(C. GoString(s))
}

In Go1.10, CGO has newly added a _GoString_ predefined C language type, which is used to represent Go language strings. Here is the improved code:

package main

//void SayHello(_GoString_ s);
import "C"

import (
"fmt"
)

func main() {<!-- -->
C. SayHello("Hello, World")
}

//export SayHello
func SayHello(s string) {<!-- -->
fmt. Print(s)
}

Slicing

Slices in golang are a bit like arrays in C, but the actual memory model is still a little different. An array in C is a continuous memory, and the value of the array is actually the first address of this memory. golang slices also contain cap and len.

Due to the difference in the underlying memory model, the pointer of the golang slice cannot be directly passed to the C function call. Instead, the first address and slice length of the internal buffer storing the slice data need to be taken out and passed, and then the array is rebuilt in the C code:

package main

/*
#include <stdint.h>

static void fill_255(char* buf, int32_t len) {
int32_t i;
for (i = 0; i < len; i ++ ) {
buf[i] = 255;
}
}
*/
import "C"
import (
"fmt"
"unsafe"
)

func main() {<!-- -->
b := make([]byte, 5)
fmt.Println(b) // [0 0 0 0 0]
C.fill_255((*C.char)(unsafe.Pointer( & amp;b[0])), C.int32_t(len(b)))
fmt.Println(b) // [255 255 255 255 255]
}

The return value of the C function

For C functions that return a value, we can get the return value normally.

/*
static int div(int a, int b) {
return a/b;
}
*/
import "C"
import "fmt"

func main() {<!-- -->
v := C.div(6, 3)
fmt.Println(v) // 2
}

The div function above implements an integer division operation, and then returns the result of the division through the return value.

However, if there is no special treatment for the case where the divisor is 0. If you want to return an error when the divisor is 0, and return a normal result the rest of the time. Because the C language does not support returning multiple results, the standard library provides a errno macro for returning error status. We can approximately see errno as a thread-safe global variable that can be used to record the status code of the latest error.

The improved div function is implemented as follows:

/*
#include <errno.h>

static int div(int a, int b) {
if(b == 0) {
errno = EINVAL;
return 0;
}
return a/b;
}
*/
import "C"
import "fmt"

func main() {<!-- -->
v0, err0 := C.div(2, 1)
fmt.Println(v0, err0) // 2 <nil>

v1, err1 := C.div(1, 0)
fmt.Println(v1, err1) // 0 The device does not recognize the command.
}

CGO also provides special support for the errno macro of the standard library: if there are two return values when CGO calls a C function, the second one returns The value will correspond to the errno error status.

The return value of the void function

There is also a function in C language that does not have a return value type, and void is used to represent the return value type. In general, we cannot obtain the return value of a void type function, because there is no return value to obtain. As mentioned in the previous example, cgo has a special treatment for errno, and the error status of the C language can be obtained through the second return value. For void type functions, this feature is still valid.

//static void noreturn() {}
import "C"
import "fmt"

func main() {<!-- -->
_, err := C. noreturn()
fmt.Println(err)
}

We can also try to get the first return value, which corresponds to the Go language type corresponding to void in C language:

//static void noreturn() {}
import "C"
import "fmt"

func main() {<!-- -->
v, _ := C. noreturn()
fmt.Printf("%#v", v) // main._Ctype_void{}
}

For the internal implementation mechanism of cgo, you can read https://www.cntofu.com/book/73/ch2-cgo/ch2-05-internal.md

Link static library and dynamic library https://www.cntofu.com/book/73/ch2-cgo/ch2-09-static-shared-lib.md