Go: Calling pointer functions in C with CGO

Jun 20 2013

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.



comments powered by Disqus