A major limitation of cgo is the lack of support for callbacks or, in more general terms, the ability for C to call a Go function. This is a major issue for bindings to event driven systems. As part of impending support for SWIG, however, the Go team has expanded cgo to provide a facility by which Go functions may be exposed to C.
In one's cgo files, exposing a function is a very simple matter: one precedes the function header with a comment starting with the word export and followed by the function name. For example:
//export Foo
func Foo() {
// ...
}
When cgo sees this it generates two new C files: _cgo_export.c and _cgo_export.h. These files provide the wrappers that enable a C program to call Go functions.
Provided with the current distribution of Go is an example program demonstrating this new feature. It is found in:
$GOBIN/misc/cgo/life
Looking through that example, though, one should notice one problem: it is not an example classic callbacks. That is, Go is not passing functions to C. Instead, the functions are merely being exposed so that C can call them. Thus the problem remains for event driven libraries such as GLUT. Callbacks are achieved via a bit of indirection.
Let us start by creating a very simple library that adds number via a callback. It has a single function, call_add, which takes a function pointer and calls it to add two integers.
// calladd.h
typedef int(*adder)(int, int);
extern void call_add(adder);
//callback.c
#include <stdio.h>
#include "calladd.h"
void
call_add(adder func) {
printf("Calling adder\n");
int i = func(3, 4);
printf("Adder returned %d\n", i);
}
Nothing to it. Compile it and turn it into a shared object:
gcc -g -c -fPIC calladd.c
gcc -shared -o calladd.so calladd.o
Now here's the interesting part: in order to pass a Go function to C we must create a wrapper that passes the wrapper to the Go function. Wonderful, eh? Two levels of indirection. . .
// funcwrap.h
extern void pass_GoAdd(void);
// funcwrap.c
#include "_cgo_export.h"
#include "calladd.h"
void
pass_GoAdd(void) {
call_add(&GoAdd);
}
Our cgo file is rather simple:
//callback.go
package callback
// #include "callgo.h"
// #include "funcwrap.h"
import "C"
func Run() {
// call the wrapper
C.pass_GoAdd()
}
//export GoAdd
func GoAdd(a, b int) int {
return a + b
}
This is still not really a callback, as we are not passing an arbitrary function. That would require yet another level of indirection (for a total of three), whereby we store a function pointer and call it within GoAdd. Hopefully in the future one level will be removed. I suspect that this could be possible if cgo interpreted any passing of an exported function to a C function as passing the wrapper, but I cannot say that I have looked deeply into the matter.
To conclude, here's the Makefile for this project (a modified version of the life example's):
CGO_LDFLAGS=_cgo_export.o calladd.so funcwrap.so $(LDPATH_linux)
CGO_DEPS=_cgo_export.o calladd.so funcwrap.so
CLEANFILES+=main
include $(GOROOT)/src/Make.pkg
funcwrap.o: funcwrap.c _cgo_export.h
gcc $(_CGO_CFLAGS_$(GOARCH)) -g -c -fPIC $(CFLAGS) funcwrap.c
funcwrap.so: funcwrap.o
gcc $(_CGO_CFLAGS_$(GOARCH)) -o $@ funcwrap.o $(_CGO_LDFLAGS_$(GOOS))
main: calladd.so install main.go
$(GC) main.go
$(LD) -o $@ main.$O