DEV Community

Łukasz Budnik
Łukasz Budnik

Posted on • Edited on

Cloud native C

Low level cloud SDKs

When you think about writing a cloud native app you probably think about higher level programming languages like Java, JavaScript, .NET, Python, Ruby, etc.

But what if you have an old C app? And you are preparing to either rehost it or even better replatform it?

Well, none of the main cloud providers offer C SDKs. AWS goes as low as C++. Which is nice.

But! All main cloud providers actually do offer Go SDKs. And with Go and its cgo tool we are already at home.

c + go = cgo

I have prepared a really simple C application which calls Go functions to upload and download an object to/from AWS S3 bucket.

The code is uploaded to my GitHub repo:

GitHub logo lukaszbudnik / cloud-native-c

cloud-native-c shows how to use AWS Go SDK from C using cgo

The Makefile is really simple, but I'm going to quickly review it:

OS:= $(shell uname -s)

ifeq ($(OS), Linux)
    build_cmd = gcc -pthread -o app app.c storage.a
endif
ifeq ($(OS), Darwin)
    build_cmd = gcc storage.a app.c -framework CoreFoundation -framework Security -o app
endif

all: app

app: storage app.c
    $(build_cmd)

storage: storage.go
    go build -buildmode=c-archive storage.go
Enter fullscreen mode Exit fullscreen mode

The storage rule compiles and builds the storage.go as a C library. It generates 2 artifacts:

  • storage.h - which contains definitions needed by the C program to call Go functions
  • storage.a - compiled C library

Other than that storage.go is just a normal go program (it has to have an empty main function). It also uses go.mod to manage dependencies (AWS SDK Go v2 libraries).

The app rule compiles and builds the app.c application. As there are small differencies between gcc on Mac and Linux I'm passing different paramters to it. For MacOS it links CoreFoundation and Security frameworks and for Linux it links pthread library. Both MacOS and Linux are built automatically on GitHub using actions running on macos-10.15 and ubuntu-20.04.

Edit: Support for Windows 2019 with MinGW was also added. Windows binaries are also built automatically on GitHub using windows-2019 runner. See repo for more details.

Source code - C

How does the application look like then?

First let's take a look at the C code:

#include <stdio.h>
#include <string.h>
#include "response.h"
#include "storage.h"

int main(int argc, char **argv)
{
  if (argc != 3)
  {
    fprintf(stderr, "This app requires exactly 2 parameters first is the path to local file and second is the S3 bucket.\n");
    fprintf(stderr, "Example:\n%s app.c my-bucket-name\n", argv[0]);
    return WRONG_PARAMETERS_ERROR;
  }

  int res = PutObject(argv[1], argv[2]);

  if (res > 0)
  {
    fprintf(stderr, "There was an error uploading the file.\n");
    return res;
  }

  char *content = GetObject(argv[1], argv[2]);
  if (content == NULL)
  {
    return FILE_DOWNLOAD_ERROR;
  }

  printf("Content of the uploaded file is:\n%s\n", content);
  Free(content);

  return SUCCESS;
}
Enter fullscreen mode Exit fullscreen mode

Nothing special. I included two header files.

  • storage.h - generated by cgo, contains C definitions of the PutObject(), GetObject() and Free() functions. These functions take and return C data types.
  • response.h - contains definitions of success and error codes. This header file is also included in Go application.

As you can see, the above source code is super simple, plain old C.

Source code - Go

Now let's take a look at the Go code.

The go code is almost plain go apart of these 4 lines:

// #include <stdio.h>
// #include <stdlib.h>
// #include "response.h"
import "C"
Enter fullscreen mode Exit fullscreen mode

The above directives are responsible for:

  • exposing any types/methods/directives defined in stdio and stdlib as C. You can reference them as: C.char (char data type) or C.free() (method to release allocated memory), etc.
  • exposing any types/methods/directives defined in custom response.h header as C. For example: C.SUCCESS or C.FILE_UPLOAD_ERROR.
  • exposing special cgo methods as C. For example to convert a *C.char to go string you can use: C.GoString() function, to create a C string (*C.char) you can use C.CString(), etc. Check cgo documentation for more.

Two points to note.

  1. It's much easier to convert types in Go then in C. So my PutObject(), GetObject(), and Free() functions take and return C types. Thanks to this I call these functions from C just like they were native C functions.
  2. Important thing is that all the memory allocated by C.* functions needs to be freed using C.free() function. That is why I also exposed Free() function so that all objects which were returned from Go to C app can be send back to Go to be freed.

Go lang app looks like this. The //export Function are required otherwise cgo won't include them in storage.h header file.

//export PutObject
func PutObject(filenameC, bucketC *C.char) int {
    filename := C.GoString(filenameC)
    bucket := C.GoString(bucketC)

    cfg, err := config.LoadDefaultConfig(context.TODO())
    if err != nil {
        fmt.Fprintf(os.Stderr, "configuration error: %v\n", err.Error())
        return C.CONFIGURATION_ERROR
    }
    (...)
}
//export GetObject
func GetObject(keyC, bucketC *C.char) *C.char {
    key := C.GoString(keyC)
    bucket := C.GoString(bucketC)
    (...)
    return C.CString(content)
}
//export Free
func Free(ptr *C.char) {
    C.free(unsafe.Pointer(ptr))
}
Enter fullscreen mode Exit fullscreen mode

Building and running

In order to build and run the application just call:

$ make all
go build -buildmode=c-archive storage.go
gcc storage.a app.c -framework CoreFoundation -framework Security -o app
$ ./app app.c my-bucket-name
File uploaded correctly, version id: Uzp7G2ehns7AtQdwpPrQoTQriXKbeldA
File version id: Uzp7G2ehns7AtQdwpPrQoTQriXKbeldA
Content of the uploaded file is:
#include <stdio.h>
#include <string.h>
#include "response.h"
#include "storage.h"
(...)
Enter fullscreen mode Exit fullscreen mode

You are now well equipped to start coding cloud native apps in C!

Top comments (0)