Sunday 23 October 2011

Experimenting with Cgo - Part 3: Errno

In this third installment of introducing cgo to novice Go programmers I want to talk about error handling. More specifically, how to handle C functions that utilize errno. At first glance one would think that this is something no harder to implement than anything else. Not so. There are a couple of "gotchas" with errno in particular you need to be aware of and little to no documentation to help you out. In particular, there was absolutely no documentation describing how to extract errno from os.Error.

  1. errno can't be accessed directly in Go. The cgo documentation clearly states why this is the case. In two words, 'thread safety'.
  2. There is a handy way to trap errno as an os.Error in Go but no obvious way to extract the actual error number.
After a lot of trial and error I finally pieced the puzzle together. First, if the C functions you're implementing set a common system error, as a function like fopen() would, then cgo provides a simple and easy way to get a Go-like error. Again, I refer you to the cgo documentation and you don't really need to look any further. If, however, you're using a library that doesn't you'll need to follow these steps, or at least some of them.

1. Map the error codes to sensible strings describing the error. Documentation on the library you're implementing usually does a good job helping here.

var errText = map[int]string {
    C.EINVAL: "Invalid mode specified",
}

2. Create a helper function to handle printing the error.

func error(e os.Error) os.Error {
    s, ok := errText[int(e.(os.Errno))]
    if ok {
        return os.NewError(s)
    }
    return os.NewError(fmt.Sprintf("Unknown error: %d", int(e.(os.Errno))))
}

This was the hard part. The os package provides Errno, which is Go's equivalent to errno. In order to extract Errno from Error you have to use a type assertion. Errno is typed to int64 which won't work with our C.int error codes so we need to then type cast it to an int.

3. Catch the error value returned by the function utilizing errno to describe errors.

    f, err := C.fopen(cpath, cmode)
    if f == nil {
        return nil, error(err)
    }

It's as simple as that. Complete source code follows:

Makefile:
include $(GOROOT)/src/Make.inc

TARG=cgoexample
CGOFILES=cgofopen.go

include $(GOROOT)/src/Make.pkg

cgofopen.go:
package cgoexample

//#include <stdio.h>
//#include <stdlib.h>
//#include <errno.h>
import "C"

import (
    "fmt"
    "os"
    "unsafe"
)

var errText = map[int]string {
    C.EINVAL: "Invalid mode specified",
}

func error(e os.Error) os.Error {
    s, ok := errText[int(e.(os.Errno))]
    if ok {
        return os.NewError(s)
    }
    return os.NewError(fmt.Sprintf("Unknown error: %d", int(e.(os.Errno))))
}

type File C.FILE

func Open(path, mode string) (*File, os.Error) {
    cpath, cmode := C.CString(path), C.CString(mode)
    defer C.free(unsafe.Pointer(cpath))
    defer C.free(unsafe.Pointer(cmode))
    
    f, err := C.fopen(cpath, cmode)
    if f == nil {
        return nil, error(err)
    }
    return (*File)(f), nil
}

func (f *File) Close() {
    if f != nil {
        C.fclose((*C.FILE)(f))
    }
}

example/Makefile:
include $(GOROOT)/src/Make.inc

TARG=cgoexample
GOFILES=fopen.go

include $(GOROOT)/src/Make.cmd

example/fopen.go
package main

import (
    "cgoexample"
    "fmt"
)

func main() {
    f, e := cgoexample.Open("foobar", "qq")
    defer f.Close()
    
    if e != nil {
        fmt.Println("Error: ", e)
    }
}

Tuesday 4 October 2011

Experimenting with Cgo - Part 2

In this second cgo article, I am going to demonstrate how to handle strings. There is already an excellent post by Andrew Gerrand, one of the Go developers at Google, on using strings in cgo. My own post will provide a slightly more complete example.

The first thing to understand is that C has no concept of a string. Unlike in Go, where a string is an actual type, a string in C is just a pointer to an array of characters. Thankfully, the "C" package has two simple functions to convert between the two types. However, there are two problems: One, converting from a Go string to a C string (character array) allocates memory to the string which is NOT garbage collected by Go. This is important! The reason for this is because Go has no way of knowing when the C library you're creating a language binding for will be done with the string. Once your function exists, any local variables will go out of scope and Go's garbage collector will at some point free the allocated memory even though the C library involved may not be finished with the string. Therefore, you have to free the memory when you know the C library is finished with it. Second, you still need a means of storing characters into a buffer and there isn't an immediately obvious way to do that.

This article assumes you've read my previous post and already know how to start a basic cgo project and have a working Go development environment. I am going to provide a very simple wrapper for the following functions from stdio.h: fopen, fclose, fputs, fgets and frewind.

Important: Absolutely no attempt was made to provide error handling in this code in order to provide absolute clarity for the code itself. While I believe proper examples should always contain error handling, handling errno and NULL return values is not easily handled in Go.

1. Include any C headers and import any libraries you will need:


// #include <stdio.h>
// #include <stdlib.h>
import "C"
import "unsafe"

Note that, when converting to a C string, you will need to free the allocated memory. Free() from stdlib.h is required to do this. You will also need to the "unsafe" library to handle C pointers.


2. In order to make stdio feel like Go, type the FILE structure:

type File C.FILE

While embedding the FILE type in a struct would allow us to create much clearer code because we could remove all the casting to/from the C and Go types Go does not allow C types to be embedded into Go structs. So, we must do things the hard way.

3. Implement the fopen() function:


func Open(path, mode string) *File {
    cpath, cmode := C.CString(path), C.CString(mode)
    defer C.free(unsafe.Pointer(cpath))
    defer C.free(unsafe.Pointer(cmode))
    
    return (*File)(C.fopen(cpath, cmode))
}

Notice first that, like the original C code, we accept strings as arguments to indicate the path and mode. The CString() function from the "C" package allocates enough memory for the Go string and then converts it to a C string. You must make sure you free the memory after using it, and the defer statement is the ideal way to accomplish this. Not only does it guarantee to free up the memory it does so after we return the value from fopen(). free() take a void pointer as an argument, so variables passed to the function must be cast to an unsafe.Pointer().


4. Implement the fgets() function:


func (f *File) Get(n int) string {
    cbuf := make([]C.char, n)
    return C.GoString(C.fgets(&cbuf[0], C.int(n), (*C.FILE)(f)))
}

I found this to be tricky to figure out until I thought about it. You need to provide a pointer to a C character array but Go provides no obvious ways to do so. Use Go's make() function to build an array of characters. You could also use a static array size but the dynamic version provides a lot more flexibility and is no more complex to implement. Use C.GoString() to convert the C string to a Go string.

5. Implement the rest of the functions. See below for the complete code.

6. Compile and install your library.

7. Compile and run a test program to complete your proof of concept.


The complete code follows:

godev/src/gostdio/gostdio.h:

package gostdio

// #include <stdio.h>
// #include <stdlib.h>
import "C"
import "unsafe"

type File C.FILE

func Open(path, mode string) *File {
    cpath, cmode := C.CString(path), C.CString(mode)
    defer C.free(unsafe.Pointer(cpath))
    defer C.free(unsafe.Pointer(cmode))
    
    return (*File)(C.fopen(cpath, cmode))
}

func (f *File) Close () {
    C.fclose((*C.FILE)(f))
    return
}

func (f *File) Get(n int) string {
    cbuf := make([]C.char, n)
    return C.GoString(C.fgets(&cbuf[0], C.int(n), (*C.FILE)(f)))
}

func (f *File) Put(str string) {
    cstr := C.CString(str)
    defer C.free(unsafe.Pointer(cstr))
    
    C.fputs(cstr, (*C.FILE)(f))
    return
}

func (f *File) Rewind() {
    C.rewind((*C.FILE)(f))
}


godev/src/cgoexample/cgoexample.go:

package main

import "gostdio"
import "fmt"

func main() {
    f := gostdio.Open("test.txt", "w+")    
    defer f.Close()
    
    f.Put("Some example text\n")
    f.Rewind()
    
    fmt.Println(f.Get(13))
}