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
mallocandfree
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
headto thenew node - Assigns key and value to the
head - Points
currentto thehead
- Creates a
- Else
- Creates a
new node - Assigns key and value to the
new node - Assigns
nextof thecurrentnode to thenew node - Points
currentto 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
nodepoint tohead - Iterates while
nodeis not NULL andkey of the nodeis not equal to the incomingkey - If
nodeis 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 -Wallcompiles the C code with gcc options: (-g) which is used to enable debug symbols and (-Wall) which is used to enable all warnings linkedlist.his included for our linked list related functions- The import “C” allows us to integrate with C code
- The comments above
import Crepresent 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.inton key and value - Invokes
putby 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.inton key - Invokes
getby 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
headis not NULL - Makes
nodepoint to head and movesheadahead - 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
lengthto get the total number of nodes in the linked list - Allocates memory to hold
leninteger values in a variable calledvalues - Creates a pointer called
nodeto point tohead - Iterates while
nodeis not NULL - Collects the value pointed to by
nodeinvalues - Returns
valueswhich 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
nodeto point tohead - Iterates while
nodeis 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.lengthto 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.freeand we useunsafe.Pointerwhich allows creating a pointer to any arbitrary type - In order to be able to invoke
C.freeorC.malloc, we need to importstdlib.hin 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.intbut we need to return a Golang slice ofGo int. So we iterate through theslice, convertC.inttoGolang int, collectvaluesand 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
