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