Getting started with CGO - Writing C in GO
Cgo is a library that is built into the standard library of Go that allows users to invoke calls to underlying C programs in their Go code. Cgo is often used as a delegator for things that are currently coded in C but don't have equivalent Go code written.
There are some instances where it becomes necessary to be able to use cgo for example:
- All of kernel code in Linux is written in C so if you have a requirement to access any of the kernel functions
- You have some legacy piece of software in C that would be difficult to port in Go
- You've exhausted the Go runtime to its limit and you need further optimization. It is very rare that we get to this particular case.
There may be some other scenarios but in general I would suggest to avoid cgo, you know why? Let's explore this in the next chapter.
Why you shouldn't use CGO?
Cgo should be used sparingly and only when there isn't an equivalent Go library available for a system. Cgo adds a few limitations to your Go programs:Needless complexity
- Very difficult in troubleshooting issues and errors
- It adds additional complexity of building and compiling C code using c-shared libraries
- Many of the Go's tooling is not available to be used in Cgo programs
- Cross-compiling doesn't work as expected or at all
- The complexity of C code
- Native Go calls are much faster than Cgo calls
- Slower build times
How to enable CGO?
The cgo tool is enabled by default for native builds on systems where it is expected to work. It is disabled by default when cross-compiling. You can control this by setting the CGO_ENABLED
environment variable when running the go tool: set it to 1 to enable the use of cgo, and to 0 to disable it.
To check the existing value you can use:
~]# go env CGO_ENABLED
1
To enable this variable you can manually execute
~]# export CGO_ENABLED=1
To make the changes permanent you can create a new file inside /etc/profile.d/go.sh
and add the following content
export CGO_ENABLED=1
So now every time the system boots, the CGO_ENABLED
will be set to 1
.
Setting up Visual Studio Code for CGO
Before we go ahead let's setup our VSC to be able to write cgo. We will use Visual Studio Code IDE to manage our cgo codes. In my previous article of Getting started with GOLANG, I have already shared detailed steps to
- Setup Visual Studio Code using RemoteSSH for Linux servers
- Install GO and dependent modules
Now to be able to use CGO we need to additionally install C module in Visual Studio Code.
Press Ctrl + Shift + X
to open the list of available extensions and search for "C" in the search bar:
Go ahead and install this extension.
Writing you first CGO code (Hello World)
The C code is accessed through the C pseudo package, and it is accessed and called using the package name followed by the identifier, for instance, C.print.
The import declaration is preceded by a series of special comments, which specify what C source file the application should import:
package example
// #include <stdio.h>
import "C"
This statement can also be a multiline comment, which can contain more include directives, like the one from the example earlier, and even actual C code directly:
package main
/*
#include <stdio.h>
#include <stdlib.h>
const char* hello_world() {
return "Hello World!";
}
*/
import "C"
import (
"fmt"
)
func main() {
fmt.Println(C.GoString(C.hello_world()))
}
We are importing two C core libraries here, which are as follows:
- stdio.h : This contains the input and output methods. We are using printf.
- stdlib.h: This contains general functions, including memory management.
Output:
$ go run main.go
Hello World!
Calling C code from GO
To use an existing C code we must source our library and functions into the go code. Let's look at this simple example where I have a multiple function in C which I will source into go code. Here is the structure of all the files used in this example:
[root@server cgo]# tree .
.
├── go.mod
├── main.go
├── mylib.c
└── mylib.h
0 directories, 4 files
Here are the content of library and helper files:
// mylib.c
#include "mylib.h"
int multiply(int a, int b) {
return a * b;
}
// mylib.h
int multiply(int a, int b);
This is my main.go
where I will be sourcing my library function and accessing the C function:
package main
import "fmt"
/*
#include <stdio.h>
#include <math.h>
#include "mylib.h"
*/
import "C"
func main() {
fmt.Println(C.multiply(10, 20))
}
Let us compile this and execute the code to verify:
~]# go build -a -o /tmp/cgo_eg .
~]# /tmp/cgo_eg
200
Explanation: The C.multiply
is not a go native function and it is using C to get multiple function from mylib.c
file.
Calling Go code from C
In the previous example we executed C code from Go application using the C package and the import statement.
Now, we will see how to call Go code from C, which requires the use of another special statement called export
. This is a comment that needs to be placed in the line above the function we want to export, followed by the name of that function:
//export mygofunc
func mygofunc() C.int {
return 0
}
The Go function needs to be declared as external in the C code. This will allow the C code to use it:
extern int mygofunc();
We can test this functionality by creating a Go app that exports a function, which is used by a C function. This gets called inside the Go main function. Here is the tree structure of my code:
# tree .
.
├── go.mod
├── main.go
└── mylib.c
0 directories, 3 files
This is mylib.c
file content:
extern int goAdd(int, int);
static int cAdd(int a, int b) {
return goAdd(a, b);
}
Here is my main.go
content where I have imported mylib.c
and added //export
to goAdd
function which needs to be called by mylib.c
:
package main
/*
#include "mylib.c"
*/
import "C"
import "fmt"
//export goAdd
func goAdd(a, b C.int) C.int {
return a + b
}
func main() {
fmt.Println(C.cAdd(1, 3))
}
Compile and execute the code:
~]# go build -a -o /tmp/cgo_eg .
~]# /tmp/cgo_eg
4
We can see in the preceding example that we have the goAdd
 function, which is exported to C with the export statement . The export name matches the name of the function, and there are no blank lines between the comment and the function.
We can notice that the types used in the signature of the exported function are not regular Go integers, but C.int variables.
Frequently Asked Questions on CGO
What is CGO?
CGO is a powerful Go tool that makes it possible to run C code in a Go application and handles communication between C code and Go code. This allows C code to be used in a Go application and to leverage the huge amount of existing C libraries.
How can you call C code from Go?
Go offers a pseudo package called C that exposed C types, such as C.int, and some functions that will convert Go strings and bytes into C character arrays, and vice versa. The comment that comes before the import C package will be interpreted as C code, and all the functions defined in it ( be it directly, or by importing files), will be available in Go as functions of the C package.
How can you use Go code in C?
If a Go function is preceded by a special comment, //export, this function will be available to the C code. It will also have to be defined as an external function in C.
What are the differences in data types between Go and C?
Even if they have different data types, C and Go share most of their built-in numeric types. Strings in Go are a built-in immutable type, but in C, they are just a character array terminated by a \0 value.
How can you edit Go values inside C code?
Using the unsafe package, you can convert data types with the same memory representation in both C and Go. You need to convert the pointer to a value in its C counterpart, and this will allow you to edit the pointer content from the C part of the application.
What are the main downfalls of CGO?
Even if it is a very powerful tool, CGO has many downsides—the performance cost of passing from C to Go, and vice versa; the fact that the compiling time increases because the C compiler gets involved in the process; and that your Go code is reliant on your C code to work, which could be harder to maintain and debug.
Troubleshooting known issues on CGO integration with VSC
could not import C (cgo preprocessing failed) (compile) go-staticcheck
This error is seen because all the libraries required to execute the C functions in your go code is not present. We will use the same example we used for our initial demonstration in this tutorial by removing the C libraries and definitions:
package main
import "C"
import "fmt"
func main() {
fmt.Println(C.GoString(C.hello_world()))
}
Now let's perform a staticcheck
:
~]# /root/go/bin/staticcheck
main.go:8:8: could not import C (cgo preprocessing failed) (compile)
As you can see my code is throwing the same warning again, it is because I have not imported the required C libraries into my go code. So let me add the required libraries and functions:
package main
/*
#include <stdio.h>
#include <stdlib.h>
const char* hello_world() {
return "Hello World!";
}
*/
import "C"
import (
"fmt"
)
func main() {
fmt.Println(C.GoString(C.hello_world()))
}
Now you can re-verify the code by executing /root/go/bin/staticcheck
and it should give empty output. Next let's execute our code:
# go run main.go
Hello World!
So now our cgo code has been successfully executed.
undefined reference to `XXXXX'
This error can be seen while trying to compile and build the go binary. For example in my case:
# go build -a -o /tmp/cgo_eg .
# cgo_eg
/tmp/go-build3805588986/b001/_x002.o: In function `cAdd':
./mylib.c:4: undefined reference to `goAdd'
collect2: error: ld returned 1 exit status
This is because I am trying to execute a go function inside my C library but the go function is not actually "exported". If you want to execute a go code using C then you must add //export above the respective function which in my case (Check above examples where full code is available):
//export goAdd
func goAdd(a, b C.int) C.int {
return a + b
}
Now let me retry to compile this code:
# go build -a -o /tmp/cgo_eg cgo_eg
No more errors seen during this execution.
Summary
In this tutorial we explored the cgo basics and how to get started by writing simple codes. We learned how to call a C code from Go and vice versa using some simple examples. We also covered a set of frequently asked questions by beginners around the cgo language. I hope this will give you a kickstart and this is just the basics while this is a really vast area to explore but atleast now you should be familiar with the basic cgo architecture and implementation design.
Further Reading
https://pkg.go.dev/cmd/cgo
https://go.dev/blog/cgo