Understand how to use C libraries in Go, with CGO
Tags: go, programming, tutorial, coding
Published: 2023-10-09 08:50:42

You probably ever heard about CGO, and that Go can use shared libraries from your system to use the power of C. But how? I will explain the process, and you'll see that it's not that complicated – in fact, it's quite simple.

This tutorial is made for very beginners, but you need to have a bit of knowledge in C programming.

I wrote this tutorial for the simple reason that I find the official documentation, as well as the tutorials I've read, a little austere. For my part, I needed to start from a very simple situation, a real "hello world", and slowly move towards the use of a shared library. So this is my vision, my way of looking at things, which I offer you here.

## What are shared libraries?

Shared libraries, also known as dynamic link libraries (DLL) on Windows or shared objects (SO) on Unix-based systems like Linux, are files containing compiled code that multiple programs can use simultaneously. Instead of having the code for a particular task duplicated in every program that needs to perform that task, the code is stored in a shared library. This way, programs can use the functions and procedures from these libraries without having to include the code directly in their files.

And Go can take advantage of this. Like Python or Rust, of course.

## What does CGO?

It is a tool that allows calling C functions and using C libraries from Go code. It serves as the bridge between Go and C languages, enabling Go programs to incorporate existing C libraries and leverage existing C codebases.

Actually, it can do this:

- **Passing Go functionalities to C** but this is not what we will see today
- **Calling C functions** directly from a "comment" above the `import "C"` statement. Or from a `.c` file inside the project.
- **Using C libraries** to use already compiled functions and types in a shared library.

CGO is provided with Go, but you'll need a C compiler like GCC on Ming (for Windows).

> CGO will use the "C" package where all C functions and variables are accessibles. It will then use the C compiler to make the link to your Go project.

## Let's try to use C in Go!

First, just to understand what does CGO, we will call a C function that we will create.

Create a project, for example in `~/Projects/testCGO` and inside this directory:

```bash
go mod init testcgo
```

Then create a `main.go` file and write this:

```go
package main

// #include <stdio.h>
// void hello() {
//    printf("Hello from C")
//}
import "C"

func main(){
    // let's call it
    C.hello()
}
```

In your terminal, type `go run .` and see the result. Yes, it says "Hello from C".

But, what does CGO here?

Actually, CGO has used the comment above the `import "C"` statement and it shared the function in the "C" package. 

> That means that the `hello()` function, developped in C, is accessible as `C.hello()` in Go. The "C" package is like a namespace where C variables and function are accessed.

But, we can do this a bit prettier. Coding in comments can be useful if we don't have too many lines of C to do, but when the project becomes substantial, it can quickly become a bit annoying. So, let's use real C source files.

In the same directory, create the `hello.c` file:

```c
#include <stdio.h>

void hello() {
    printf("Hello from C in another file")
}
```

And, a `hello.h` file to declare the function:

```c
void hello();
```

Then, in your `main.go` file, replace the content to get this:

```go
package main

// #include "hello.h"
import "C"

func main(){
    // let's call it
    C.hello()
}
```

This way, we're using the `include` statement, which is a C inclusion syntax. CGO has no problem:

```terminal
go run main.go
Hello from C in another file
```

One more time, CGO takes the comments above the `import "C"` and because the `#include` statement is a valid C call, it compiles the C file without any problem.

We've just seen the base. Having C code on one side and a Go project on the other, we now know how to connect the two. But of course, there will be more restrictive things to manage.

## How to work with C types?

Let's change the `hello.c` file to accept an argument and say hello to someone.

```c
#include <stdio.h>

// say hello to the name
void hello(char* name) {
    printf("Hello %s\n", name);
}
```

Of course, change the header file to decralre the function:

```c
void hello(char*);
```

Then, change the `main.go` file to now try to say hello to John:

```go
package main

// #include "hello.h"
import "C"

func main() {
    C.hello("John")
}
```

This will fail...

```
./main.go:7:10: cannot use "John" (untyped string constant) as *_Ctype_char value in argument to (_Cfunc_hello)
```

> That's something very important to keep in mind, we need to cast vars from and to Go and C.

Go ease the work with many types, like array, pointers, and strings. When you need to send or receive a variable from or to C, the types are not exactly the same. So we need to "cast" types. But, no panic, after a while you will do it naturally.

So, how to fix this?

We need to modify the Go `string` to `char*` type. We can use `C.char` type but that needs to manually allocate memory. Instead, there is a `C.CString` type which is a bit easier to use.

```go
func main() {
    name := C.CString("Gopher")
    C.hello(name)
}
```

And now, it works!

> This is the "complex" part of using CGO, you will need to convert, cast, manipulate the variables type to ensure that it will work.
> And because it's C, there is no garbage collector for C variables, so you need to free memory when needed (using `C.free()`)


# So let's use a C library!

Now that we have seen how CGO can compile C code, let's try to tell it to link shared libraries.

For the example, I will use the very simple "libuuid".

You need to install the devel package of the library to get the header files. On Fedora, that was a simple `sudo dnf install libuuid-devel` command line.

To be able to generate a UUID, you need to read the documentation of the library (yes... RTFM...) - of course, I already did it and I can explain how to generate a UUID.

```c
// In C:
// we need a uuid_t variable to initalize
uuid uuid_t;
// then we generate the random string. I use the random form
// but you can use other generate methods.
uuid_generate_random(uuid);
// To get a uuid string, we need to "unparse"
char uuid_str[37];
uuid_unparse_lower(uuid, uuid_str);
// In the uuid_str char*, there is a uuid
```

OK, so let's try.

We will include the `uuid/uuid.h` file, commonly in `/usr/include` on Linux, that CGO will find. And let's use the types and functions from this header file.

```go
package main

// #include <uuid/uuid.h>
import "C"
import "fmt"

func main() {
    var uuid C.uuid_t
    var uuid_str *C.char
    uuid_str = (*C.char)(C.malloc(37))
    C.uuid_generate_random(uuid)
    C.uuid_unparse(uuid, uuid_str)
    fmt.Println(C.GoString(uuid_str))
}
```

This will fail...

The first problem, here, is that the `typdedef` doesn't work. We need to read the error to understand that, actually, `uuid_t` is a `uchar*`. But, not exactly... Actually, reading the header file, you'll see that it's a `char[16]`.

> Remember, in C, a `char*` is like a `char[]` (I'm grossly oversimplifying here). 
> But, libuuid declares the `uuid_t` with 16 chars of size, that means that, using pointer form, we need to allocate the memory with `malloc`.

So let's change the line to:
```go
var uuid *C.uchar
uuid = (*C.uchar)(C.malloc(16))
```

> You need to know a bit of C to work. Here, what I do is a simple C `uuid = (uchar*)malloc(16)` transposed with "C" package in Go. 

Let's run one more time and... 

```
/usr/lib/golang/pkg/tool/linux_amd64/link: running gcc failed: exit status 1
/usr/bin/ld: /tmp/go-link-645695464/000001.o: in function `_cgo_b1eb6bfc450b_Cfunc_uuid_generate_random':
/tmp/go-build/cgo-gcc-prolog:49: undefined reference to `uuid_generate_random'
/usr/bin/ld: /tmp/go-link-645695464/000001.o: in function `_cgo_b1eb6bfc450b_Cfunc_uuid_unparse':
/tmp/go-build/cgo-gcc-prolog:62: undefined reference to `uuid_unparse'
collect2: error: ld returned 1 exit status
```

> That's now the time to use the "linker". Of course, we need to tel the compiler to use the `libuuid.so` shared library.

Understand the problem. Earlier, we used our own C source files that are compiled with CGO. But, now, **we want to use "already" compiled sources to a ".so" library** (or `.dll` for Windows). The header files are there to only provide function declaration (name, arguments and return types).

> This is a common thing in C/C++ - that makes compilation very smart and fast, because we don't need to compile the libraries. We only "link" them.

To inform CGO to link the shared library, we can use a specific flags to the compiler. For `libuuid` it's a simple `-luuid` (understand `-l uuid`) to append. This will link `libuuid.so` to our binary. And Go proposes to specify these arguments inside the comments, as a `#cgo:` statement.

Above the inclusion of the header file, only append a special instruction:

```go
// #cgo LDFLAGS: -luuid
// #include <uuid/uuid.h>
import "C"

```

And now it's OK

```
 go run  .
78137255-35a3-4f61-af7c-e04bf9eb513a
```

> That's it, you have a UUID generated by a C shared library.

The entire source code is:

```go
package main

// #cgo LDFLAGS: -luuid
// #include <uuid/uuid.h>
import "C"
import "fmt"

func main() {
    var uuid *C.uchar
    var uuid_str *C.char
    uuid = (*C.uchar)(C.malloc(16))
    uuid_str = (*C.char)(C.malloc(37))
    C.uuid_generate_random(uuid)
    C.uuid_unparse(uuid, uuid_str)
    fmt.Println(C.GoString(uuid_str))
}
```

It works very well, but is it practical? No...

## Make it better

Calling C functions, with on-the-fly type casting, is impractical, unattractive and not at all easy to maintain.

Using C, the functions are way simpler to use:

```c
uuid_t uuid;
uuid_generate_random(uuid);
char *str = malloc(37); // because 36 chars + \0
uuid_unparse_lower(uuid, str);
// and I can return "str" variable
// that contains a UUID
```

Then...

What do we really want? **A function that gives us a UUID**. So we're going to do something very practical: 

> code a function in C that will make our work easier, and just make sure we have access to it in our Go program.

OK, try:

```go
package main

// #cgo LDFLAGS: -luuid
//
// #include <uuid/uuid.h>
// #include <stdlib.h>
//
// // create a uuid function in C to return a uuid char*
// char* _go_uuid() {
//   uuid_t uuid;
//   uuid_generate_random(uuid);
//   char *str = malloc(37);
//   uuid_unparse_lower(uuid, str);
//   return str;
// }
import "C"
import "fmt"

// uuid generates a UUID using the C shared library.
// It returns a Go string.
func uuid() string {
	return C.GoString(C._go_uuid())
}

func main() {
    // and now it's simple to use
    myuuid := uuid() // this is a go string now
    fmt.Println(myuuid)
}
```

Of course, we could create the `_go_uuid()` function in a C source file and create a `.h` file to declare our function. Then, include `go_uuid.h`.

What we did here is very common when we want to bind shared libraries to Go. We create some helper functions to cast types and to call C function without asking the user to use the `C` package by itself.

And this is how https://github.com/go-gst/go-gst, https://github.com/go-gl/glfw, and even https://fyne.io/ are using system libraries to propose a lot of functionalities.

## Reminder

So, what you need to keep in mind when you want to use shared libraries:

- the "C" package gives access to C functions, types and variables
- you can include header files using comments **above the import of "C" pacakge"**
- you can provide `LDFLAGS` and `CFLAGS` to the compiler using the `#cgo` statement in comments
- you often need to cast types to Go types, or Go types to C
- you can create helpers in comments, in C, to ease the use of the librairies

## Conclusion

C isn't the only language that lets you generate shared libraries. You can, of course, generate them in Go, Rust, Python, etc. But to date, it's C that's most widely used to generate libraries.

Having the possibility to use C, or calling C functions from a shared library opens Go to a wide range of powerful functionalities.

Obviously, **we prefer libraries developed entirely in Go**. This avoids dependence on a library that the user will have to intall on his system, or by sharing this library with him (in the form of `.so` or `.dll`). As Go is a language that normally uses static compilation, it's a "bit of a pity" to force the passage. But it's very useful in practice. For example, the very powerful Gstreamer library would be very complicated to recreate entirely in Go. It was created in C and works very well on many platforms. So here, having a dependency on this library is an excellent solution to open streaming sound and video to Go.

You'll need some knowledge of C to be able to link a library in Go. But you don't have to be a specialist. You just need to find the right variable cast, and create a helper function from time to time.

In any case, I hope that my article has opened the way for you, cleared up a few misunderstandings, and that you'll be able to use shared libraries with Go!