Handling C++ exceptions in Go

Cgo is a mechanism that allows Go packages call C code. The Go compiler enables cgo for every .go source file that imports a special pseudo package "C". The text in the comment before the import "C" line is treated as a C code. You can include headers, define functions, types and variables - everything a normal C code can do:

package main /* #include <stdio.h> void foo(int x) { printf("x: %d

", x); } */ import "C" func main () { C . foo ( C . int ( 123 )) // x: 123 }

The identifiers declared in the embedded C code can be accessed using the "C" package - e.g. write C.foo(C.int(123)) to call the function foo. Pretty straightforward, right? This opens the door to a huge amount of code that was written in C or was written in any other language and provides C bindings.

Almost all libraries play by the rules, but some libraries that are written in C++ may throw exceptions. C doesn't support exceptions, therefore there is no way to catch them in C or Go/cgo. C function should never throw an exception, but nothing stops a developer from writing code that does it. A good example of such a library is a machine learning project Vowpal Wabbit - it's written in C++ and provides a C interface, which may throw an exception from time to time:

package main // #cgo LDFLAGS: -lvw_c_wrapper // #include <stdlib.h> // #include <vowpalwabbit/vwdll.h> import "C" import "unsafe" func main () { cArgs := C . CString ( "invalid args" ) defer C . free ( unsafe . Pointer ( cArgs )) C . VW_InitializeA ( cArgs ) // exception }

Unhandled C++ exception on VW_InitializeA call just crashes the program:

SIGABRT: abort PC=0x7efc793a7428 m=0 sigcode=18446744073709551610 signal arrived during cgo execution goroutine 1 [syscall, locked to thread]: runtime.cgocall(0x4500b0, 0xc420049f58, 0xc420049f58) /usr/lib/go-1.8/src/runtime/cgocall.go:131 +0xe2 fp=0xc420049f28 sp=0xc420049ee8 main._Cfunc_VW_InitializeA(0x193c620, 0x0)

While cgo lets us call only C code, we still can link any C++ file to our program. This gives us an ability to create a C wrapper for VW_InitializeA that handles the exceptions. We can use a structure to return both the original return value and a pointer to the exception message (good old C doesn't support tuples or multiple return values):

// vw_wrapper.h #ifdef __cplusplus extern "C" { #endif #include <stdlib.h> #include <vowpalwabbit/vwdll.h> typedef struct VWW_HANDLE_ERR { VW_HANDLE handle ; const char * pstrErr ; } VWW_HANDLE_ERR ; VW_DLL_MEMBER VWW_HANDLE_ERR VW_CALLING_CONV VWW_InitializeA ( const char * pstrArgs ); #ifdef __cplusplus } #endif

The wrapper code is only a few lines, save it in the project directory as a .cpp file:

#include <string.h> #include <exception> #include "vw_wrapper.h" VW_DLL_MEMBER VWW_HANDLE_ERR VW_CALLING_CONV VWW_InitializeA ( const char * pstrArgs ) { VWW_HANDLE_ERR result = { 0 }; try { result . handle = VW_InitializeA ( pstrArgs ); } catch ( std :: exception & e ) { result . pstrErr = strdup ( e . what ()); } return result ; }

The Go compiler will compile and link the C++ source with the program. Don't forget to explicitly free the strErr pointer if its value is not nil - Go garbage collector doesn't know how to deal with C pointers:

package main // #cgo LDFLAGS: -lvw_c_wrapper // #include <stdlib.h> // #include "vw_wrapper.h" import "C" import ( "log" "unsafe" ) func main () { cArgs := C . CString ( "invalid args" ) defer C . free ( unsafe . Pointer ( cArgs )) ret := C . VWW_InitializeA ( cArgs ) if ret . pstrErr != nil { defer C . free ( unsafe . Pointer ( ret . pstrErr )) log . Println ( "VW error: " , C . GoString ( ret . pstrErr )) } }

Now, whenever the original VW_InitializeA throws an exception, the C++ wrapper will catch it and return the error message as a part of the VWW_HANDLE_ERR structure.