Go: Calling pointer functions in C with CGO
The Go programming language provides the cgo
tool for calling C code inside of a Go program. At compile time, Go can even compile C code into the Go executable.
One of the gotchas for using this functionality is that from within Go, you cannot execute C function pointers. I ran into this while working on Go bindings for the C BerkeleyDB library.
Here I give what is (according to some helpful souls in the #go-nuts
IRC channel) the best practice for calling such functions.
<!--break-->
Here's a simple example of creating a new BerkeleyDB database handle:
package berkeleydb
/\*
#cgo LDFLAGS: -ldb
#include <db.h>
\*/
import "C"
type BDB struct {
db \*C.DB
}
func NewDB() (*BDB, int) {
var db \*C.DB
err := C.db_create(&db, nil, 0)
if err > 0 {
return nil, int(err)
}
return &BDB{db}, nil
}
Inside of NewDB()
, we access various C symbols using the C
package. Take a close look at the import "C"
statement. The comment above it contains special directives that are passed through to cgo
, which then uses them to compile and link the associated C code. In this case, I import the db.h
header file, which contains the BerkeleyDB public API.
All of this is relatively straightforward… but now that we have a database handle, how do we open the database? The C API for this looks something like this:
db->open(DB *db, DB_ENV *env)
(Disclaimer: I have vastly simplified the arguments list for open()
.)
The important detail is how the function is called: The db
struct has a pointer to a function (open
). As powerful as cgo
is, it cannot resolve the function pointer at compile time.
So what's the solution? The best route to dealing with this is to let C handle it. C functions can be embedded into the comment above import "C"
, or we can just add it to a separate .c
file (with a .h
file).
In either case, all we need to call db->open()
is a simple C wrapper like this:
int go_db_open(DB \*dbp, DB_TXN *txnid) {
return dbp->open(dbp, txnid);
}
Note that all we're actually doing is creating a C function that calls the "method-like" open()
function attached to the database handle. Now from inside the Go code, we can execute db->open()
with our wrapper:
func (handle \*BDB) Open(filename string, env C.DB_ENV) int {
db := handle.db
ret := C.go_db_open(db, env)
return int(ret)
}
Now when we call C.go_db_open()
, it executes the wrapper function we defined, attaching the newly opened database file to our existing database handle.
If you want to take a look at the complete code, see the PerkDB GitHub repo. AlekSi has a great compendium of cgo
examples in his CGO-By-Example GitHub repo.