
This is a small example of using C libraries in Go. Go is relatively young in the world of programming languages, and although it’s very popular and has many available modules, there’s occasionally a need to interface with a pre-existing C library. I came across this need when attempting some video manipulation on my GoPro videos. To be honest, this probably would have been easier to write in Python, but I am learning Go, so I attempted it in Go. I rapidly found myself down a rabbit hole where the bindings I chose for FFmpeg in Go weren’t 100% implemented (or I just didn’t know what I was doing!), so it was time for some learning.
This is how I ended up having to write my first bit of C code since university. This implementation is meant to resemble a very simple version of what I actually need to write for video processing, but this will serve as a ‘Hello World’ test run.
The first file to create is the header file, person.h. This will define our types and method signatures.
/*
* person.h
* Copyright (C) 2019 Tim Hughes
*
* Distributed under terms of the MIT license.
*/
#ifndef PERSON_H
#define PERSON_H
typedef struct APerson {
const char * name;
const char * long_name;
} APerson ;
APerson *get_person(const char * name, const char * long_name);
#endif /* !PERSON_H */
Next, we need to create an implementation of our method in a .c file. This file can be called anything, but we will call it person.c to make it memorable.
/*
* person.c
* Copyright (C) 2019 Tim Hughes
*
* Distributed under terms of the MIT license.
*/
#include <stdlib.h>
#include "person.h"
APerson *get_person(const char *name, const char *long_name){
APerson *fmt = malloc(sizeof(APerson));
fmt->name = name;
fmt->long_name = long_name;
return fmt;
};
This is now code-complete as far as our libperson library is concerned, so we can compile it and then write a main function to test it out.
To generate a .so shared object library file, you need to compile it with the -fPIC (position independent code) flag.
gcc -o person.o -c -fPIC person.c
This creates an object file, .o, which we can then use to create a .so file.
gcc -o libperson.so -shared person.o
You can see the difference using the file command:
$ file person.o libperson.so
person.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
libperson.so: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=8dfc4bd062c85c79406059ad7e1df5dd46f9d191, not stripped
I have done it the long way so that you can see what is going on, but you can do it all in one step. Best practice is to also add the -Wall flag for warnings and the -g flag for debugging output.
gcc -o libperson.so -Wall -g -shared -fPIC person.c
Now that we have a .so file, we can create a main function to call the method in the shared library.
/*
* person.c
* Copyright (C) 2019 Tim Hughes
*
* Distributed under terms of the MIT license.
*/
#include <stdio.h>
#include "person.h"
int main(int argc, char** argv)
{
APerson * of;
of = get_person("tim", "tim hughes");
printf("Hello C world: My name is %s, %s.\n", of->name, of->long_name);
return 0;
}
To compile our hello.c file against libperson.so, we need to specify the directory to look in with -L and the name of the library with the -l flag.
gcc -o hello -L. -lperson hello.c
Normally, you would move the file into /usr/lib, /usr/lib64, or a similar location. For testing, we can override the LD_LIBRARY_PATH environment variable to point to our current directory.
$ LD_LIBRARY_PATH=. ./hello
Hello C world: My name is tim, tim hughes
Now that we have a functioning shared library, we can create our Go code. To access a C library in Go, we use the “C” pseudo-package. This is explained in more detail in the Cgo documentation.
Just before importing “C”, we include a comment (known as the preamble). This comment can contain any C code we want, which is used as a header when the Go compiler processes the C parts of the code. In this case, we simply include the person.h file. We can also pass instructions to the Cgo compiler here. The important one is LDFLAGS, which tells the linker where to look for our libperson.so file, similar to how we handled hello.c.
Here is our Go code:
//
// main.go
// Copyright (C) 2019 Tim Hughes
//
// Distributed under terms of the MIT license.
//
package main
/*
#cgo CFLAGS: -g -Wall
#cgo LDFLAGS: -L. -lperson
#include "person.h"
*/
import "C"
import (
"fmt"
)
type (
Person C.struct_APerson
)
func GetPerson(name string, long_name string) *Person {
return (*Person)(C.get_person(C.CString(name), C.CString(long_name)))
}
func main(){
var f *Person
f = GetPerson("tim", "tim hughes")
fmt.Printf("Hello Go world: My name is %s, %s.\n", C.GoString(f.name), C.GoString(f.long_name))
}
You will see that we have created a type Person that is of type C.struct_APerson. Then we create a function GetPerson that calls the get_person function in libperson. The return value is then type-cast to *Person so that we have a normal Go pointer backed by a C struct.
If you try to print the members of the struct directly using something like fmt.Println(f.name), you’ll get a hex number like 0x1aa67e0—the integer representing the pointer. You’ll need to convert these to Go strings using the suitably named C.GoString() function.
To make it more convenient to access the struct members, we can add accessor methods to the Person type. This is especially important if you’re making a Go library for others to use, as it hides the implementation details and C types from the application code.
Here is the updated main.go file with accessor methods and an updated main function:
//
// main.go
// Copyright (C) 2019 Tim Hughes
//
// Distributed under terms of the MIT license.
//
package main
/*
#cgo CFLAGS: -g -Wall
#cgo LDFLAGS: -L. -lperson
#include "person.h"
*/
import "C"
import (
"fmt"
)
type (
Person C.struct_APerson
)
func (p *Person) Name() string {
return C.GoString(p.name)
}
func (p *Person) LongName() string {
return C.GoString(p.long_name)
}
func GetPerson(name string, long_name string) *Person {
return (*Person)(C.get_person(C.CString(name), C.CString(long_name)))
}
func main(){
var f *Person
f = GetPerson("tim", "tim hughes")
fmt.Printf("Hello Go world: My name is %s, %s.\n", C.GoString(f.name), C.GoString(f.long_name))
fmt.Printf("Hello Go world: My name is %s, %s.\n", f.Name(), f.LongName())
}
The final code is available at https://github.com/timhughes/cgoexample.
I hope this has been useful reading!