Shared Library¶
We define a simple module that contains a particular subset
of Fortran features. Many of them are exported with unmangled
names (via bind(c)
) so the ABI can be used in a predictable way
independent of the compiler or platform.
Public Interface¶
-
int
KNOB
¶ The first public symbol is a mutable global:
Fortran Implementation:
integer(c_int) :: KNOB = 1337
KNOB
does not have a bound name (i.e. it may be mangled) and it is expected to be accessed via a public getter and setter.
-
void
turn_knob
(int *new_value)¶ Fortran Implementation:
! NOTE: This is missing ``bind(c, name='view_knob')`` because ! the ``f2py`` parser has issues for some reason. pure function view_knob() result(knob_value) integer(c_int) :: knob_value knob_value = KNOB end function view_knob subroutine turn_knob(new_value) bind(c, name='turn_knob') integer(c_int), intent(in) :: new_value KNOB = new_value end subroutine turn_knob
C Signature:
void turn_knob(int *new_value);
As noted in the remark, the
view_knob()
getter also does not have a bound name because of the way afunction
(vs. asubroutine
) is parsed byf2py
(an issue has been filed).
In addition to a mutable global, there are two user defined types
exposed and unmangled, hence each acts as a C struct
.
-
UserDefined
¶ Fortran Implementation:
type, bind(c) :: UserDefined real(c_double) :: buzz real(c_double) :: broken integer(c_int) :: how_many end type UserDefined
C Signature:
typedef struct UserDefined { double buzz; double broken; int how_many; } UserDefined;
-
double
buzz
¶
-
double
broken
¶
-
int
how_many
¶
-
double
-
DataContainer
¶ Fortran Implementation:
type, bind(c) :: DataContainer real(c_double) :: data(4, 2) end type DataContainer
C Signature:
typedef struct DataContainer { double data[8]; } DataContainer;
-
double[8]
data
¶
-
double[8]
-
void
foo
(double bar, double baz, double *quux)¶ The first subroutine exported by the public interface is an implementation of
f(x, y) = x + 3.75 y
.Fortran Implementation:
subroutine foo(bar, baz, quux) bind(c, name='foo') real(c_double), intent(in), value :: bar, baz real(c_double), intent(out) :: quux quux = bar + 3.75_dp * baz end subroutine foo
C Signature:
void foo(double bar, double baz, double *quux);
It accepts the inputs by value. Since pass-by-reference is the default behavior, an equivalent method is provided (though not as part of the unmangled ABI):
subroutine foo_by_ref(bar, baz, quux) real(dp), intent(in) :: bar, baz real(dp), intent(out) :: quux call foo(bar, baz, quux) end subroutine foo_by_ref
-
void
foo_array
(int *size, double *val, double *two_val)¶ Next, we define a method that accepts a variable size array and places twice the values of that array in the return value:
Fortran Implementation:
subroutine foo_array(size_, val, two_val) bind(c, name='foo_array') integer(c_int), intent(in) :: size_ real(c_double), intent(in) :: val(size_, 2) real(c_double), intent(out) :: two_val(size_, 2) two_val = 2.0_dp * val end subroutine foo_array
C Signature:
void foo_array(int *size, double *val, double *two_val);
-
void
make_udf
(double *buzz, double *broken, int *how_many, UserDefined *quuz)¶ The next subroutine creates an instance of the
UserDefined
data type, but smuggles the result out as raw bytes. The total size issize(buzz) + size(broken) + size(how_many) = 2 c_double + c_int
. This is 20 bytes on most platforms, but as a struct it gets padded to 24 due to word size.Fortran Implementation:
subroutine make_udf(buzz, broken, how_many, as_bytes) bind(c, name='make_udf') real(c_double), intent(in) :: buzz, broken integer(c_int), intent(in) :: how_many character(c_char), intent(out) :: as_bytes(24) ! Outside of signature type(UserDefined), target :: made_it made_it%buzz = buzz made_it%broken = broken made_it%how_many = how_many ! NOTE: We need ``sizeof(as_bytes) == sizeof(made_it)`` as_bytes = transfer(made_it, as_bytes) end subroutine make_udf
C Signature:
void make_udf(double *buzz, double *broken, int *how_many, UserDefined *quuz);
This concept of “data smuggling” is necessary for the use of user defined types with
f2py
, since it has no support for them.
-
void
udf_ptr
(intptr_t *ptr_as_int)¶ A related way to smuggle data for use with
f2py
is to allocate memory for the struct and then pass a pointer to that memory as an opaque integer. Once this is done, the Fortran subroutine can convert the integer into a Fortranpointer
and then write to the memory location owned by the foreign caller:Fortran Implementation:
subroutine udf_ptr(ptr_as_int) bind(c, name='udf_ptr') integer(c_intptr_t), intent(in) :: ptr_as_int ! Outside of signature type(c_ptr) :: made_it_ptr type(UserDefined), pointer :: made_it made_it_ptr = transfer(ptr_as_int, made_it_ptr) call c_f_pointer(made_it_ptr, made_it) made_it%buzz = 3.125_dp made_it%broken = -10.5_dp made_it%how_many = 101 end subroutine udf_ptr
C Signature:
void udf_ptr(intptr_t *ptr_as_int);
This approach is problematic because it is so brittle. The memory must be handled by the caller rather than by Fortran directly. If the subroutine were responsible for the memory, the object would likely be allocated on the stack and the memory location re-used by subsequent calls to the subroutine.
-
void
make_container
(double *contained, DataContainer *container)¶ The next subroutine takes an array as input and sets the
data
attribute of a returnedDataContainer
instance as the input. This acts as a check that the operation happens as a data copy rather than a reference copy.Fortran Implementation:
subroutine make_container(contained, container) & bind(c, name='make_container') real(c_double), intent(in) :: contained(4, 2) type(DataContainer), intent(out) :: container container%data = contained end subroutine make_container
C Signature:
void make_container(double *contained, DataContainer *container);
-
void
just_print
(void)¶ The
just_print()
subroutine simply prints characters to the screen. However, printing requireslibgfortran
, which slightly complicates foreign usage.Fortran Implementation:
subroutine just_print() bind(c, name='just_print') print *, "======== BEGIN FORTRAN ========" print *, "just_print() was called" print *, "======== END FORTRAN ========" end subroutine just_print
C Signature:
void just_print(void);
Object File¶
For some foreign usage of example
, we’ll directly use a compiled
object file. To create example.o
:
$ gfortran \
> -J fortran/ \
> -c fortran/example.f90 \
> -o fortran/example.o
Shared Object¶
It’s more common for foreign usage of native code to be done via a shared object file:
$ gfortran \
> -shared -fPIC \
> -J fortran/ \
> fortran/example.f90 \
> -o fortran/example.so
Here, we manually build a “position independent” shared library in the
same directory as the source. However, in many cases, native code comes
with an installer that puts the library in a standard place, e.g. a
symlink to libatlas
can be found in /usr/lib/libatlas.so
. Shared
object files are typically named lib{pkg}.so
so that they can be
included by the compiler with -l{pkg}
. The compiler uses a default list of
“search directories” when finding such shared libraries.
References¶
- Examples of user-defined types
- StackOverflow question about user-defined types
- The
sphinx-fortran
project was started to provideautodoc
capabilities for Fortran libraries, but it is not actively maintained (as of this writing, August 2018) - The AutoAPI redesign of
autodoc
will hopefully mature into a capable way of documenting Fortran code (and code from other languages) using Sphinx - The FORD (FORtran Documentation) project is a modern way to generate documentation for Fortran code, though it is “Yet Another” documentation generator (example documentation)
- The
breathe
project / library seeks to be a bridge Python XML-based doxygen and Sphinx, though in practice the formatting of doxygen produced documentation is not in line with typical Sphinx documentation