cgo is an amazing technology which allows Go programs to interoperate with C libraries. Given a Go source file written with some special features, cgo outputs Go and C files that can be combined into a single Go package.
C code and Go code live in two different universes, cgo traverses the boundary between them. The transition is not free and depending on where it exists in your code, the cost could be inconsequential, or substantial.
There are some cases where cgo is unavoidable, most notably where you have to interoperate with a graphics driver or windowing system what is only available as a binary blob.
1 | package rand |
cgo directives
cgo is not a language nor a compiler. It’s a Foreign Function Interface (FFI), a mechanism we can use in Go to invoke functions and services written in a different language, specifically C.
The comment block right above the import “C” instruction is called a “preamble” and can contain actual C code, in this case an header inclusion. Once imported, the “C” pseudo-package lets us “jump” to the foreign code. You can build the example by invoking go build, the same as if it was plain Go.
1 | // #include <stdio.h> |
CFLAGS, CPPFLAGS, CXXFLAGS, FFLAGS, LDFLAGS may be defined with pseudo #cgo
directives within these comments to tweak the behavior of the C, C++ or Fortran compiler. Values defined in multiple directives are concatenated together. The directive can include a list of build constraints limiting its effect to systems satisfying one of the constraints.
cgo recognizes this comment. Any lines starting with #cgo
followed by a space character are removed; these become directives for cgo. The remaining lines are used as a header when compiling the C parts of the package. In this case those lines are just a single #include
statement, but they can be almost any C code. The cgo
directives are used to provide flags for the compiler and linker when building the C parts of the package.
1 | // #cgo CFLAGS: -DPNG_DEBUG=1 |
Alternatively, CPPFLAGS and LDFLAGS may be obtained via the pkg-config tool using a ‘#cgo pkg-config:’ directive followed by the package names. For example:
1 | // #cgo pkg-config: png cairo |
1 | // #cgo LDFLAGS -L${SRCDIR}/libs -l foo |
1 | // #cgo LDFLAGS -L/go/src/foo/libs -l foo |
-L
is the path to the directories containing the libraries. A search path for libraries.-l
is the name of the library you want to link to.
For instance, if you want to link to the library ~/libs/libabc.a
you’d add:
1 | -L$(HOME)/libs -labc |
1 | | file | compiler | |
tips about pkg-config
pkg-config: return metainformation about installed libraries.
The pkg-config
program is used to retrieve information about installed libraries in the system. It is typically used to compile and link against one or more libraries. Here is a typical usage scenario in a Makefile:
1 | program: program.c |
pkg-config
retrieves information about packages from special metadata files. These files are named after the package, and has a .pc
extension. On most systems, pkg-config
looks in /usr/lib/pkgconfig
, /usr/share/pkgconfig
, /usr/local/lib/pkgconfig
and /usr/local/share/pkgconfig
for these files. It will additionally look in the colon-separated (on Windows, semi-colon-separated) list of directories specified by the PKG_CONFIG_PATH
environment variable.
The package name specified on the pkg-config
command line is defined to be the name of the metadata file, minus the .pc
extension. If a library can install multiple versions simultaneously, it must give each version its own name (for example, GTK 1.2 might have the package name “gtk+” while GTK 2.0 has “gtk-2.0”).
In addition to specifying a package name on the command line, the full path to a given .pc
file may be given instead. This allows a user to directly query a particular .pc
file.
/usr/local/lib/pkgconfig/libuv.pc
1 | prefix=/usr/local/Cellar/libuv/1.20.0 |
basic data type conversion
1 | | C | Go | |
1 | | C | Go | |
string conversion between C and Go
Unlike Go, C doesn’t have an explicit string type. Strings in C are represented by a zero-terminated array of chars. Conversion between Go and C strings is done with the C.CString, C.GoString, and C.GoStringN functions. These conversions make a copy of the string data.
1 | // Go string to C string |
1 | // C string to Go string |
memory management
Memory allocations made by C code are not known to Go’s memory manager. When you create a C string with C.CString (or any C memory allocation) you must remember to free the memory when you’re done with it by calling C.free.
The call to C.CString returns a pointer to the start of the char array, so before the function exits we convert it to an unsafe.Pointer and release the memory allocation with C.free. A common idiom in cgo programs is to defer the free immediately after allocating (especially when the code that follows is more complex than a single function call), as in this rewrite of Print:
1 | func Print(s string) { |
A standard way to do this follows.
1 | // #include <stdlib.h> |
handle C return error in Go
Any C function (even void functions) may be called in a multiple assigned context to retrieve both the return value (if any) and the C errno variable as an error (use _ to skip the result value if the function returns void). For example.
1 | /* |
1 | n, err := C.sqrt(-1) |
call Go code in C code
Using //export in a file places a restriction on the preamble: since it is copied into two different C. Output files, it must not contain any definitions, only declarations.
1 | //export MyFunction |
1 | extern int64 MyFunction(int arg1, int arg2, GoString arg3); |