Go provides a pseudo package called “C” to interface with C libraries. Its not very straightforward how to do this though. In this tutorial, we’ll go over creating a simple C function, and calling it from go. After that, we’ll move on to a slightly more complex example involving C structs.
Keep in mind that using cgo is not always best the course to take. You’ll be losing many of the type safety benefits and cross-compilation targets that go provides. It should be used as a last resort: when you’re absolutely sure that it is not possible to achieve your objective in native go code.
We will be creating a C library that generates a greeting message, and interfacing to it through go.
Create a new folder in your $GOPATH/src
. I’ve called it cgo-tutorial. Inside this folder, create a header file called greeter.h with the following content:
#ifndef _GREETER_H
#define _GREETER_H
int greet(const char *name, int year, char *out);
#endif
We create a function called greet that takes a string for the name, an integer for the year, and a pointer to write the resulting greeting to. It returns an integer which specifies how many characters were written, and assumes the required buffer space in out
has already been allocated.
Now, write the definition for the greet
function in a file called greeter.c
:
#include "greeter.h"
#include <stdio.h>
int greet(const char *name, int year, char *out) {
int n;
n = sprintf(out, "Greetings, %s from %d! We come in peace :)", name, year);
return n;
}
We write the greeting message to the buffer of characters pointed to by out
. Note that we don’t check for buffer overflow here, for the sake of simplicity.
Now we can run gcc -c greeter.c
to make sure that our library actually compiles.
Create a file called main.go
, and add the following to the top:
package main
// #cgo CFLAGS: -g -Wall
// #include <stdlib.h>
// #include "greeter.h"
import "C"
import (
"fmt"
"unsafe"
)
The import "C"
line allows us to interface with C code through our go program. The comments above it is actual C code that can be consumed by the rest of the code. We include stdlib.h so that we can call malloc and free; and greeter.h for our greeter function. In addition to this, we can add directives for cgo to use. For instance, adding the line // #cgo CFLAGS: -g -Wall
compiles the C files with the gcc options -g
(enable debug symbols) and -Wall
(enable all warnings) enabled. Notice how the line for importing C
is outside the import block. Without performing the imports this way, the includes and other directives won’t be considered.
Next, we’ll start with the main function:
func main() {
name := C.CString("Gopher")
defer C.free(unsafe.Pointer(name))
year := C.int(2018)
}
C.CString
is a cgo function that takes a go string and returns a pointer to a C char, ie: *C.char
. This allows us to pass strings to C code. Note that this function uses malloc
to create the space for the string, so we need to call the C function free
once we are done with the string to prevent memory leaks. We use C.int
to create an integer that can be passed to C code.
ptr := C.malloc(C.sizeof_char * 1024)
defer C.free(unsafe.Pointer(ptr))
Here, we run malloc from the C standard library to allocate memory for 1024 chars. We will be using this memory as the buffer space into which we’ll have our greeting written. Cgo ensures that in the event that the malloc fails, the entire program will crash. So we cannot explicitly handle this error, and simply expect that the request will always be fulfilled. As with the string, we’ll have to call free once we no longer need the buffer.
The function C.malloc
returns an object of type unsafe.Pointer
. An unsafe pointer can be cast to a pointer of any type. We’ll be casting it to a pointer to char before passing it to our greet function.
size := C.greet(name, year, (*C.char)(ptr))
Here, we call the greet function from our C code. We pass the the name, the year, and the pointer to the output buffer(cast to *C.char
since that is what out library function expects). Our function returns the size of the greeting message.
b := C.GoBytes(ptr, size)
fmt.Println(string(b))
Here, we convert the C buffer to a go []byte
object. The cgo function C.GoBytes
does this for us, using the pointer and the size of the written data. The byte slice returned does not share memory with the bytes we allocated using malloc. We can safely call free on ptr
and continue to use the byte slice returned by C.GoBytes
.
Putting it all together:
package main
// #cgo CFLAGS: -g -Wall
// #include <stdlib.h>
// #include "greeter.h"
import "C"
import (
"fmt"
"unsafe"
)
func main() {
name := C.CString("Gopher")
defer C.free(unsafe.Pointer(name))
year := C.int(2018)
ptr := C.malloc(C.sizeof_char * 1024)
defer C.free(unsafe.Pointer(ptr))
size := C.greet(name, year, (*C.char)(ptr))
b := C.GoBytes(ptr, size)
fmt.Println(string(b))
}
Now run this in a shell:
go install
$GOPATH/bin/cgo-tutorial
If all goes well, you should see:
Greetings, Gopher from 2018! We come in peace :)
Now, we’ll rewrite the greet function, but using a struct instead of passing the name and year as arguments.
Add the struct to greeter.h
, and modify the declaration of the greet function accordingly:
struct Greetee {
const char *name;
int year;
};
int greet(struct Greetee *g, char *out);
Modify the greet function in greeter.c
to work with the new struct:
int greet(struct Greetee *g, char *out) {
int n;
n = sprintf(out, "Greetings, %s from %d! We come in peace :)", g->name, g->year);
return n;
}
Everything is mostly the same, but we use the name and year from the struct now.
Next, we have to update our main.go
function to use the new definitions of the greet function:
name := C.CString("Gopher")
defer C.free(unsafe.Pointer(name))
year := C.int(2018)
g := C.struct_Greetee{
name: name,
year: year,
}
ptr := C.malloc(C.sizeof_char * 1024)
defer C.free(unsafe.Pointer(ptr))
size := C.greet(&g, (*C.char)(ptr))
b := C.GoBytes(ptr, size)
fmt.Println(string(b))
The only differences are in how we created a struct, and how we called the function. We can access any C struct using C.struct_
followed by the name of the struct. We can then initialize the members as we would in a normal go struct. In this case, we set up the name and year arguments.
When calling the greet function, we pass the pointer to the struct, since that is what the function expects. Upon running this, we should again see:
Greetings, Gopher from 2018! We come in peace :)
Always keep in mind: you may not need cgo. Evaluate all your options, and try sticking with a pure go implementation. If you decide that you do need to use cgo, and you need more information, the cgo documentation is a good place to look.