Go¶
Fortran can be invoked directly from Go code by using the cgo
language extension. A simple example is provided in the Golang
source tree, which is quite helpful. If the Fortran source is in the
same directory as a Go library, cgo automatically builds the Fortran
files and includes them.
We define the Go package example in the golang/src directory. The
Fortran source and the C headers are side-by-side with the Go package:
$ tree golang/src/example/
golang/src/example/
├── example.f90 -> ../../../fortran/example.f90
├── example.go
└── example.h -> ../../../c/example.h
0 directories, 3 files
Package¶
Within the package, we first declare the package to use cgo and
include the relevant header file:
// #include "example.h"
import "C"
We start by defining user-friendly equivalents of C.struct_UserDefined and
C.struct_DataContainer
type UserDefined struct {
Buzz float64
Broken float64
HowMany int32
}
type DataContainer struct {
Data *[8]float64
}
func (udf *UserDefined) String() string {
return fmt.Sprintf(
"%T(%f, %f, %d)",
udf, udf.Buzz, udf.Broken, udf.HowMany,
)
}
Adding just_print() to the package interface is just a matter of
making C.just_print public:
func JustPrint() {
C.just_print()
}
When passing in *float64 (i.e. pointer types), the underlying
values can be passed along to C.make_udf without having to copy
any data, e.g. via (*C.double)(buzz). The foreign call will populate
the fields of a C.struct_UserDefined, which will need to be converted
to normal Go types when a UserDefined value is created for the return.
To avoid copying when constructing a UserDefined object, we dereference
a field value (e.g. &quuz.buzz), convert the reference to a non-C
pointer type (e.g. (*float64)(&quuz.buzz)) and then dereference the
value:
func MakeUDF(buzz, broken *float64, howMany *int32) *UserDefined {
var quuz C.struct_UserDefined
C.make_udf(
(*C.double)(buzz),
(*C.double)(broken),
(*C.int)(howMany),
&quuz,
)
return &UserDefined{
*(*float64)(&quuz.buzz),
*(*float64)(&quuz.broken),
*(*int32)(&quuz.how_many),
}
}
When dealing with array types, the first value in a slice is dereferenced and
then converted into a C pointer (e.g. (*C.double)(&val[0])):
func FooArray(size *int32, val []float64) []float64 {
twoVal := make([]float64, len(val), cap(val))
C.foo_array(
(*C.int)(size),
(*C.double)(&val[0]),
(*C.double)(&twoVal[0]),
)
return twoVal
}
When calling udf_ptr(), a UserDefined value must be created,
dereferenced, cast to unsafe.Pointer and then cast again to uintptr:
madeIt := example.UserDefined{}
ptrAsInt := uintptr(unsafe.Pointer(&madeIt))
example.UDFPtr(&ptrAsInt)
Only then can the uintptr be converted to a C.intptr_t:
func UDFPtr(ptrAsInt *uintptr) {
C.udf_ptr(
(*C.intptr_t)(unsafe.Pointer(ptrAsInt)),
)
}
func MakeContainer(contained []float64) *DataContainer {
var container C.struct_DataContainer
C.make_container(
(*C.double)(&contained[0]),
&container,
)
dataPtr := (*[8]float64)(unsafe.Pointer(&container.data))
return &DataContainer{dataPtr}
}
In the case of view_knob(), the mangled name must be used:
func ViewKnob() int32 {
knobValue := C.__example_MOD_view_knob()
return (int32)(knobValue)
}
func TurnKnob(newValue *int32) {
C.turn_knob(
(*C.int)(newValue),
)
}
Output¶
The Go example can be run via go run. As with C and C++, the
gfortran search path may need to be explicitly provided (with -L flags). This
can be done with the CGO_LDFLAGS environment variable.
$ go run golang/main.go
------------------------------------------------------------
quux = foo(1.000000, 16.000000) = 61.000000
------------------------------------------------------------
quuz = make_udf(1.250000, 5.000000, 1337)
= *example.UserDefined(1.250000, 5.000000, 1337)
------------------------------------------------------------
foo_array(
4,
[[3.000000, 4.500000],
[1.000000, 1.250000],
[9.000000, 0.000000],
[-1.000000, 4.000000]],
) =
[[6.000000, 9.000000],
[2.000000, 2.500000],
[18.000000, 0.000000],
[-2.000000, 8.000000]]
------------------------------------------------------------
ptrAsInt = &madeIt
ptrAsInt = 842350544096 // 0xc4200144e0
udf_ptr(&ptrAsInt) // Set memory in ``madeIt``
&madeIt = *example.UserDefined(3.125000, -10.500000, 101)
------------------------------------------------------------
contained =
[[0.000000, 4.000000],
[1.000000, 9.000000],
[1.000000, 2.000000],
[3.000000, 1.000000]]
container = make_container(contained)
container.Data =
[[0.000000, 4.000000],
[1.000000, 9.000000],
[1.000000, 2.000000],
[3.000000, 1.000000]]
&contained = 842350560256 // 0xc420018400
&container = 842350518320 // 0xc42000e030
container.Data = 842350560320 // 0xc420018440
------------------------------------------------------------
just_print()
======== BEGIN FORTRAN ========
just_print() was called
======== END FORTRAN ========
------------------------------------------------------------
view_knob() = 1337
turn_knob(42)
view_knob() = 42