In addition to its compact syntax and good concurrency support, Go has the advantage of being able to call C code. You can write C code directly in Go source code, or you can refer to external libraries in C. This allows you to rewrite the language where there are performance bottlenecks, or where certain features are missing in Go and third parties but are readily available in C. This way, you can rewrite the code where performance bottlenecks are encountered, or some features are missing in Go and third parties, but C language has ready-made libraries that you can use directly. The official Cgo blog https://blog.golang.org/c-go-cgo and the command line documentation https://golang.org/cmd/cgo/ explain Cgo, but some parts of it are not clear enough or are not mentioned.

Inline C code

The following example writes C code directly to the Go source code by introducing a non-existent package C, and then writing the C code on top of the introduction, noting that it can only be written on top of the C package. A method add is defined, which is then used in the Go code via C.add.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

/* Here is the C code */

// int add(int a, int b) {
//     return a + b;
// }
import "C"

import "fmt"

func main() {
    a := C.int(1)
    b := C.int(2)
    value := C.add(a, b)
    fmt.Printf("%v\n", value)
    fmt.Printf("%v\n", int(value))
}

Separate C source code

Smaller C code can be easily used by inlining, but larger code can be written independently of C code.

  • Directory structure:

    1
    2
    3
    4
    
    example/
    foo.h
    foo.c
    main.go
    
  • foo.h

    1
    
    int add(int, int);
    
  • foo.c

    1
    2
    3
    
    int add(int a, int b) {
        return a + b;
    }
    
  • main.go

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    package main
    
    // #include "foo.h"
    import "C"
    
    import "fmt"
    
    func main() {
        a := C.int(1)
        b := C.int(2)
        value := C.add(a, b)
        fmt.Printf("%v\n", value)
    }
    

The above example splits the C code into foo.h and foo.c. Go code just needs to introduce foo.h to call the add method.

If the main package refers to more than one file, you can’t run it with the go run command, you have to use go build. go build detects the files referenced by cgo, and the .c .h files are compiled together.

1
2
3
cd example/
go build -o out
./out

Calling external libraries

Directory structure:

1
2
3
4
5
example2/
  main.go
  foo/
    foo.h
    foo.c

The C code is the same as before, but moved to the foo directory to force Go not to compile them. Assume that foo is the external library we need to use.

Compile the static library first, generating the libfoo.a static link library in the foo directory.

1
2
3
cd foo
gcc -c foo.c -o foo.o
ar -crs libfoo.a foo.o
  • main.go

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    package main
    
    // #cgo CFLAGS: -I./foo
    // #cgo LDFLAGS: -L./foo -lfoo
    // #include "foo.h"
    import "C"
    import "fmt"
    
    func main() {
        value := C.add(C.int(1), C.int(2))
        fmt.Printf("%v\n", value)
    }
    

Calling external libraries requires the #cgo pseudo directive, which can specify compilation and linking parameters, such as CFLAGS, CPPFLAGS, CXXFLAGS, FFLAGS and LDFLAGS. CFLAGS can configure C compiler parameters, where -I can specify the header directory, such as “-I/path/to/include”. LDFLAGS can be configured to refer to libraries, where -L specifies the directory of the referenced library and -l specifies the library name. If the referenced header file or library is in the default system directory (e.g. /usr/include, /usr/local/include and /usr/lib, /usr/local/lib) then the directory can be left unspecified.

The #cgo directive can have platform-restricted arguments, e.g. for linux or darwin platforms only, see https://golang.org/pkg/go/build/#hdr-Build_Constraints for details. You can also use ${SRCDIR} instead of the source directory.

1
2
3
// #cgo linux  CFLAGS: -I$./foo
// #cgo darwin CFLAGS: -I$./another_foo
// #cgo LDFLAGS: -L${SRCDIR}/foo -lfoo

Two tips for array pointers

C often uses array pointers as arguments and return values, the following example shows how Go creates an array pointer and converts an array pointer to a slice.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package main

// #include <stdio.h>
// #include <stdlib.h>
//
// int pass_array_pointer(int *in, int n) {
//     int sum = 0;
//     for(int i = 0; i < n; i++) {
//         sum += in[i];
//         printf("%d\n" , in[i]);
//     }
//     return sum;
// }
//
// int *return_array_pointer(int n) {
//     int *out;
//     out = (int *)(calloc(3, sizeof(int)));
//     for(int i = 0; i < n; i++) {
//         out[i] = i * 2;
//     }
//     return out;
//  }
import "C"
import (
    "fmt"
    "reflect"
    "unsafe"
)

// Convert slice to a C array pointer
func pass() {
    in := []C.int{1, 2, 3, 4}
    inPointer := unsafe.Pointer(&in[0])
    inC := (*C.int)(inPointer)

    value := C.pass_array_pointer(inC, 4)
    fmt.Println(value)
}

// Convert a C array pointer to a slice
func get() {
    n := 4
    outC := C.return_array_pointer(4)
    defer C.free(unsafe.Pointer(outC))

    // 参考 https://github.com/golang/go/issues/13656#issuecomment-165867188
    sh := reflect.SliceHeader{uintptr(unsafe.Pointer(outC)), n, n}
    out := *(*[]C.int)(unsafe.Pointer(&sh))
    for _, v := range out {
        fmt.Println(v)
    }
}

func main() {
    pass()

    get()
}