- errno can't be accessed directly in Go. The cgo documentation clearly states why this is the case. In two words, 'thread safety'.
- 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)
}
}
The link should be http://golang.org/cmd/cgo/ instead of http://golang.org/cmd/godoc/
ReplyDeleteI would use something like "C.strerror(errno)" to translate the error number into a string.
ReplyDeleteUsing strerror() would be fine except for libraries which don't define strings for their error codes, like the ncurses library, which is the whole purpose of this article. Using strerror_r() to define all the strings is far more complicated more prone to error than the above described method.
ReplyDelete