Go

The Go compiler has been able to produce shared libraries since version 1.5. So how does it work ? First let’s write a Go function we want to share. The Go standard library has packages for image manipulation, so what about a function to load an image and return its size:

package main

import (
        "C"
        "image"
        _ "image/jpeg"
        _ "image/png"
        "os"
)

//export ImgutilGetImageSize
func ImgutilGetImageSize(path *C.char, w *uint, h *uint) *C.char {
        file, err := os.Open(C.GoString(path))
        if err != nil {
                return C.CString(err.Error())
        }
        defer file.Close()

        img, _, err := image.Decode(file)
        if err != nil {
                return C.CString(err.Error())
        }

        rect := img.Bounds()
        *w = uint(rect.Dx())
        *h = uint(rect.Dy())

        return nil
}

func main() {}

As you can see, the code has to be part of the main package, with an empty main function. The C package has to be imported. Exported functions must have the export annotation.

Another subtlety to notice is that Go pointers cannot be returned by exported functions. So instead of returning an error value as usual, we directly return an error string. If there is no error, we return a null pointer.

We can then run:

go build -o libimgutil.so -buildmode=c-shared imgutil.go

This generates two files: a shared library and a C header. The prototype of the exported function is:

extern char* ImgutilGetImageSize(char* p0, GoUint* p1, GoUint* p2);

C

We can use the shared library in C:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "libimgutil.h"

int
main(int argc, char **argv) {
    char *path, *err;
    GoUint w, h;

    if (argc < 2) {
        fprintf(stderr, "missing argument\n");
        return 1;
    }

    path = strdup(argv[1]);
    err = ImgutilGetImageSize(path, &w, &h);
    free(path);

    if (err != NULL) {
        fprintf(stderr, "error: %s\n", err);
        free(err);
        return 1;
    }

    printf("%s: %llux%llu\n", argv[1], w, h);

    return 0;
}

Then we use clang to compile it:

clang -o test test.c -L. -limgutil

It works as expected.

Python

The ctypes package makes it easy to use foreign functions in Python:

from ctypes import *
import sys

if len(sys.argv) < 2:
    print("missing argument", file=sys.stderr)
    sys.exit(1)

path = sys.argv[1]

lib = cdll.LoadLibrary("libimgutil.so")

get_image_size = lib.ImgutilGetImageSize
get_image_size.argtypes = [c_char_p, POINTER(c_ulonglong), POINTER(c_ulonglong)]
get_image_size.restype = c_char_p

w = c_ulonglong()
h = c_ulonglong()

err = get_image_size(path.encode('utf-8'), byref(w), byref(h))
if err != None:
    print("error: %s" % (str(err, encoding='utf-8')))
    sys.exit(1)

print("%s: %dx%d" % (path, w.value, h.value))