This is a small example on using C libraries in Go. Go is a relative newcomer in the world of programming languages and although it is very popular and there are many modules available for it there is the occasional need to interface with a pre existing C library. I came across the need for this when attempting to do 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 to use for ffmpeg in Go are not 100% implemented or I just dont know what I am doing so time to do 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 am going to have to actually write to get some video processing done but this will be more of 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 a 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 then we will 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 a 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 people can see what is going on but you
can do it all in one step and best practice is to also add in the -Wall
flag
to get warnings and also the -g
flag to get debugging output when compiling.
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 .so
/*
* 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 the libperson.so
we need to specify the
directory to look in with -L
and then 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
or /usr/lib64
or somewhere
similar but for testing we can override the LD_LIBRARY_PATH
environment
variable to person to the directory we are in.
$ 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 can use the “C” pseudo package. This is explained in
more detail at https://golang.org/cmd/cgo/#hdr-Using_cgo_with_the_go_command
Just before the import of C we can put a comment, otherwise knows as the
preamble. This comment may any snippets of C code that we want and it will be
used as a header when the go compiler compiles the C parts of the code. In this
case we just the person.h
file. In the preamble we can also pass some
instructions to the cgo compiler. The important on here is the LDFLAGS
that
tell the linker where to look for our libperson.so
file in the same way we did
when we compiled 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 returns a
calls the get_person
function in libperson
. The return value of the function
call is then type cast to *Person
so that we have a normal Go pointer that is
backed by a C struct.
If at this point you try and print the members of the struct using something
like fmt.Println(f.name)
you will get a hex number that looks like 0x1aa67e0
which is the integer representing a pointer. You will need to convert them to a
Go string using the suitable named C.GoString()
function.
To more convenient to access the members of the struct we can add accessors
methods to the Person
type. These are especially important if you are making a
go library that someone else is going to use so that they don’t need to know the
implementation underneath and don’t need to worry about C types when they are
coding their application.
Here is the updated main.go
file with the accessors methods added and the main
function updated to use them.
//
// 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())
}
Final code is available at https://github.com/timhughes/cgoexample
I hope this has been useful reading.