Go Cgo for C code
Cgo lets us include C code in Go.
Inline C in go
You can write C code in go files by adding it as comments above an import "C"
statement:
package main
import "fmt"
/*
#include <stdint.h>
uint8_t shift(uint8_t x, int y) {
return x << y;
}
*/
import "C"
func main() {
in, n := uint8(0x10), 2
out := C.shift(C.uint8_t(in), C.int(2))
fmt.Printf("Shifted %#x by %d, got %#x\n", in, n, out)
}
If we run this, we print:
Shifted 0x10 by 2, got 0x40
We can also use installed C libraries directly in go:
package main
import (
"fmt"
"math"
)
// #include <math.h>
import "C"
func main() {
fmt.Printf(" C π: %.9f\nGo π: %.9f\n", C.M_PI, math.Pi)
}
Gives us:
C π: 3.141593000
Go π: 3.141592654
Compiling C files
We may want to import C from source. Using the shift example from above, we can organise our go project into:
|-main.go
|-shift.c
|-shift.h
In main.go
:
package main
import "fmt"
// #include "shift.h"
import "C"
func main() {
in, n := uint8(0x10), 2
out := C.shift(C.uint8_t(in), C.int(2))
fmt.Printf("Shifted %#x by %d, got %#x\n", in, n, out)
}
In shift.h
:
#include <stdint.h>
uint8_t shift(uint8_t x, int y);
In shift.c
:
#include "shift.h"
uint8_t shift(uint8_t x, int y) {
return x << y;
}
And we should be able to compile and run as normal, getting the same result as before:
Shifted 0x10 by 2, got 0x40
Calling Go in C
We may want to leverage Go to enhance the capabilities of our C libraries. Similar to the previous section, we will organize our code as follows:
|-main.go
|-gocaller.c
|-gocaller.h
In this example, we will use Go’s json
library to encode some values provided by our C code.
In our main.go
file, we will use the //export
directive to export a go function that our C code can use:
package main
import (
"encoding/json"
)
// #include "gocaller.h"
import "C"
//export Encode
func Encode(a C.int, b C.int, c C.int) *C.char {
m := map[string]C.int{"a": a, "b": b, "c": c}
bytes, _ := json.Marshal(&m)
var result *C.char = C.CString(string(bytes))
// Warning: `result` is a memory leak!
return result
}
func main() {
C.addAndEncodeWithGo(1, 2)
}
Inside gocaller.h
, we have:
void addAndEncodeWithGo(int a, int b);
And inside gocaller.c
, we #include "_cgo_export.h"
so that we can call our Encode
function from Go.
#include <stdio.h>
#include "gocaller.h"
#include "_cgo_export.h"
void addAndEncodeWithGo(int a, int b) {
char* encoded = Encode(a, b, a + b);
printf("Encoded message `%s`\n", encoded);
}
If we run this, our C code calls Go’s Encode
function, and then prints the json object to the terminal:
Encoded message `{"a":1,"b":2,"c":3}`
Notes on CGo
- There is no garbage collection for C pointers. The example for calling Go from C had a memory leak, where we assigned a
*C.char
pointer.- This should always be freed with
C.free
, usually usingdefer
to ensure the lifetime is contained:var cstr *C.char = C.CString(gostr) defer C.free(unsafe.Pointer(cstr))
- This should always be freed with
- Compiled Go code using Cgo is only useful for the machine (OS/arch) that the code is compiled on.
- Go cross compilation in Cgo is usually not worth the effort
- Compiling your Go code for other machines is limited by the availablity of the C libraries you need.
- Compared to pure Go, Cgo has very little native debugging support