Invoking C Code from Golang
Posted on December 21, 2021 • 8 minutes • 1522 words
Table of contents
The article attempts to explore Golang’s “C” package which allows invoking C code from Golang. Before we get into the idea of invoking C code from Golang, let’s see a use-case where this might be needed.
Interfacing with an existing C library
Let’s consider that we wish to develop a storage engine for pmem (persistent memory) in Golang. In order to develop this, we might want to use pmdk - persistent memory development kit which is written in C. This effectively means we want a way to bridge Golang and C code; invoke C code from Golang.
“C” package in Golang
Go provides a package called “C” to interface with C code. Some features provided by this package include -
- standard C numeric types
- access to structs defined in C
- access to function like
malloc
andfree
C Code
Let’s start by creating a linked list in C which will be later invoked from Golang code. Let’s start with linkedlist.h
file which defines a struct called Node
, and a set of operations supported by linked list.
struct Node {
int key;
int value;
struct Node *next;
};
void put(int key, int value);
int get(int key);
Our Node
struct has a key, and a value which are of type integer
and a pointer to the next node. It also supports 2 behaviors put
and get
.
Let’s add linkedlist.c
and add put
to begin with.
#include "linkedlist.h"
#include <stdlib.h>
struct Node *head = NULL;
struct Node *current = NULL;
void put(int key, int value) {
if (head == NULL) {
head = (struct Node*)malloc(sizeof(struct Node));
head -> key = key;
head -> value = value;
current = head;
}
else {
struct Node *next = (struct Node*)malloc(sizeof(struct Node));
next -> key = key;
next -> value = value;
current -> next = next;
current = next;
}
}
put
does the following -
- If head is NULL
- Creates a
new node
- Points
head
to thenew node
- Assigns key and value to the
head
- Points
current
to thehead
- Creates a
- Else
- Creates a
new node
- Assigns key and value to the
new node
- Assigns
next
of thecurrent
node to thenew node
- Points
current
to thenew node
- Creates a
Let’s add get
which will return a value by key.
int get(int key) {
struct Node* node = head;
while (node->key != key && node != NULL) {
node = node -> next;
}
if (node == NULL) {
return -1;
}
return node -> value;
}
get
does the following -
- Makes a temporary variable
node
point tohead
- Iterates while
node
is not NULL andkey of the node
is not equal to the incomingkey
- If
node
is NULL, returns -1 - Else returns the value pointed by
node
Time to invoke C code from Golang
Let’s create a file linkedlist.go
which will provide Put
and GetBy
behaviors. These functions will internally invoke C functions.
package linkedlist
// #cgo CFLAGS: -g -Wall
// #include "linkedlist.h"
import "C"
This is how the beginning of our go file looks like -
- Go package is
linkedlist
- Adding the line
#cgo CFLAGS: -g -Wall
compiles the C code with gcc options: (-g) which is used to enable debug symbols and (-Wall) which is used to enable all warnings linkedlist.h
is included for our linked list related functions- The import “C” allows us to integrate with C code
- The comments above
import C
represent the actual C code that will be consumed by the rest of our golang code
Let’s add Put
in Golang.
func Put(key, value int) {
C.put(C.int(key), C.int(value))
}
Golang Put
does the following -
- Creates a C int by using
C.int
on key and value - Invokes
put
by usingC.put
, passing C.int
Let’s add GetBy
.
func GetBy(key int) int {
return int(C.get(C.int(key)))
}
Golang GetBy
does the following -
- Creates a C int by using
C.int
on key - Invokes
get
by usingC.get
- Convert the return value received from
C.get(C.int(key))
toGolang's int
Putting it all together -
package linkedlist
// #cgo CFLAGS: -g -Wall
// #include "linkedlist.h"
import "C"
func Put(key, value int) {
C.put(C.int(key), C.int(value))
}
func GetBy(key int) int {
return int(C.get(C.int(key)))
}
Let’s add a Golang test to see if this integration works or not.
package linkedlist_test
import (
"reflect"
"testing"
)
import "linkedlist"
func TestPutsASingleKeyValue(t *testing.T) {
linkedlist.Put(10, 100)
value := linkedlist.GetBy(10)
if value != 100 {
t.Fatalf("Expected %v, received %v", 100, value)
}
}
Let’s Run the test using go test
, and it should work.
Freeing the linked list
We would like to add another test which adds multiple key value pairs and gets a value by key. Before we do that, we would like to start with a fresh linked list
for each test. In order to do that let’s add a behavior close
in C linked list which frees all the nodes in the linked list.
void close() {
struct Node* node;
while (head != NULL) {
node = head;
head = head->next;
free(node);
}
}
close
does the following -
- Makes a temporary variable
node
- Iterates while
head
is not NULL - Makes
node
point to head and moveshead
ahead - Frees the memory pointed to by
node
Let’s invoke C's close
from Golang
func Close() {
C.close()
}
Let’s edit the existing test case and add a new one.
func TestPutsASingleKeyValue(t *testing.T) {
defer linkedlist.Close()
linkedlist.Put(10, 100)
value := linkedlist.GetBy(10)
if value != 100 {
t.Fatalf("Expected %v, received %v", 100, value)
}
}
func TestPutsMultipleKeyValues(t *testing.T) {
defer linkedlist.Close()
linkedlist.Put(10, 100)
linkedlist.Put(20, 200)
linkedlist.Put(30, 300)
value := linkedlist.GetBy(20)
if value != 200 {
t.Fatalf("Expected %v, received %v", 200, value)
}
}
As a part of these tests, we are now closing the linked list by invoking defer linkedlist.Close()
which will internally free all the nodes.
Let’s get all the values
We would like to return all the values from linked list. As a first step, we would like to return a pointer to an integer from C code to Golang.
Let’s code getAllValues
in C.
int* getAllValues() {
int len = length();
int* values = malloc(len * sizeof(int));
struct Node* node = head;
int index = 0;
while (node != NULL) {
values[index] = node -> value;
node = node -> next;
index = index + 1;
}
return values;
}
getAllValues
does the following -
- Invokes
length
to get the total number of nodes in the linked list - Allocates memory to hold
len
integer values in a variable calledvalues
- Creates a pointer called
node
to point tohead
- Iterates while
node
is not NULL - Collects the value pointed to by
node
invalues
- Returns
values
which is a pointer to an integer
Let’s also add length
function -
int length() {
int count = 0;
struct Node* node = head;
while (node != NULL) {
node = node -> next;
count = count + 1;
}
return count;
}
length
does the following -
- Creates a pointer called
node
to point tohead
- Iterates while
node
is not NULL - Increments count for each node
- Returns count
Time to invoke getAllValues
from Golang.
func GetsAllValues() []int {
length := int(C.length())
intPointer := C.getAllValues()
defer C.free(unsafe.Pointer(intPointer))
slice := (*[1 << 28]C.int)(unsafe.Pointer(intPointer))[:length:length]
var values []int
for index := 0; index < length; index++ {
values = append(values, int(slice[index]))
}
return values
}
GetAllValues
does the following -
- Invokes
C.length
to get the total number of linked list nodes - Invokes
C.getAllValues()
to get a pointer to an integer - We would like to free the memory held by the pointer returned from
C.getAllValues()
. In order to do that, we need to invokeC.free
and we useunsafe.Pointer
which allows creating a pointer to any arbitrary type - In order to be able to invoke
C.free
orC.malloc
, we need to importstdlib.h
in Golang code - So far we have a pointer or let’s say a C pointer. We need a way to convert that to Go slice.
The expression slice := (*[1 << 28]C.int)(unsafe.Pointer(intPointer))[:length:length]
is made up of 3 parts -
1. unsafePointer := unsafe.Pointer(intPointer) creates an unsafe pointer
2. arrayPtr := (*[1 << 28]C.int)(unsafePointer).`*[1 << 28]C.YourType` doesn't do anything itself, it is a type.
Specifically, it is a pointer to an array of size 1 << 28, of C.YourType values.
Statement 2) converts unsafePointer to a pointer of the type *[1 << 28]C.int
3. slice := arrayPtr[:length:length], slices the array into a Go slice
- We now have a Golang slice of type
C.int
but we need to return a Golang slice ofGo int
. So we iterate through theslice
, convertC.int
toGolang int
, collectvalues
and return it
Let’s quickly add a golang test for GetAllValues
.
func TestGetAllValues(t *testing.T) {
defer linkedlist.Close()
linkedlist.Put(10, 100)
linkedlist.Put(20, 200)
linkedlist.Put(30, 300)
values := linkedlist.GetAllValues()
expected := []int{100, 200, 300}
if !reflect.DeepEqual(expected, values) {
t.Fatalf("Expected %v, received %v", expected, values)
}
}
That is it, run the test and get all the values from linked list.
Code
The code for this article is available here