Creating shared libraries in Go
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))