An example of implementing the Fortran bindings for the CSDMS Basic Model Interface (BMI).
This is a forked / detached version of the bmi-fortran-example adapter at:
https://github.com/csdms/bmi-example-fortran
The sole purpose of this repos was to build dynamic libraries for both Windows and (RHEL8) Linux, and to create a JNA interop for Java with some help of Claude Code.
Files not relevant to the Deltares BMI application have been removed, and the github fork was detached as this will not serve to create a pull requet upstream.
The bmi_main.f90 and test/ folder are preserved from the upstream
CSDMS bmi-example-fortran repository as Fortran usage examples.
They require CMake/fpm to build and are not part of the Java interop
build pipeline.
This is an example of implementing a BMI for a simple model that solves the diffusion equation on a uniform rectangular plate with Dirichlet boundary conditions. Tests and examples of using the BMI are provided. The model is written in Fortran 90. The BMI is written in Fortran 2003.
This repository is organized with the following directories:
- heat
- Holds the model and a sample main program
- bmi_heat
- Holds the BMI for the model and a main program to run the model through its BMI
- test
- Unit tests for the BMI-ed model
- example
- Examples of controlling the model through its BMI
This repos contains a github workflow, build.yml to use the Github actions platform for building the binaries.
This fork adds a C/Java interoperability layer on top of the standard BMI
FORTRAN implementation, following the NOAA-OWP NextGen iso_c_fortran_bmi
pattern. This allows the model to be called from Java via
JNA (Java Native Access)
inside Deltares FEWS.
Java (FEWS BMI adapter)
│ JNA – interop/FortranModelJnaLibrary.java
▼
libbmi_heat.so / .dll
├── register_bmi.f90 model-specific factory function
├── iso_c_bmif_2_0.f90 generic C-interop layer (all 50+ BMI functions)
│ uses ↓
├── bmif_2_0_iso.f90 BMI abstract type with ISO C integer kinds
│ extends ↓
├── bmi.f90 CSDMS BMI v2.0 abstract spec
│
└── bmi_heat.f90 concrete heat-model BMI implementation
uses ↓
heat.f90 2D heat equation physics
register_bmi(void** handle) allocates a bmi_heat instance, wraps it in a
thin Fortran box type, and returns c_loc(box) as an opaque void*. Every
other BMI function takes this handle, recovers the Fortran object with
c_f_pointer, and dispatches polymorphically.
finalize(void** handle) both runs the BMI finalize method and
deallocates the model. There is no separate bmi_destroy() — do not use
the handle after calling finalize.
ABI note: every function except register_bmi receives the handle as
type(c_ptr), intent(in) without the Fortran VALUE attribute.
Without VALUE, Fortran bind(C) passes by reference, so the C ABI is
void** throughout. In JNA this maps to PointerByReference — do not
unwrap with .getValue() before calling BMI methods.
| Platform | Container / runner | Compiler | Output |
|---|---|---|---|
| Linux | AlmaLinux 8 Docker (docker/Dockerfile) |
Intel ifx 2025.2 | libbmi_heat.so (statically linked Intel runtime) |
| Windows | windows-latest |
Intel ifx 2025.2 | libbmi_heat.dll + 4 Intel runtime DLLs |
import bmi.model.FortranModelJnaLibrary;
import bmi.model.FortranString;
import com.sun.jna.Native;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;
// Load the shared library
FortranModelJnaLibrary lib =
Native.load("bmi_heat", FortranModelJnaLibrary.class);
// Allocate the model
PointerByReference handleRef = new PointerByReference();
lib.register_bmi(handleRef);
// All methods take handleRef directly — do NOT call handleRef.getValue()
// Initialize
lib.initialize(handleRef, new FortranString("/path/to/config.cfg").toBytes());
// Find grid size for a variable
IntByReference gridRef = new IntByReference();
lib.get_var_grid(handleRef, new FortranString("plate_surface__temperature").toBytes(), gridRef);
IntByReference sizeRef = new IntByReference();
lib.get_grid_size(handleRef, gridRef, sizeRef);
// Run one step and retrieve results
lib.update(handleRef);
float[] dest = new float[sizeRef.getValue()];
lib.get_value_float(handleRef, new FortranString("plate_surface__temperature").toBytes(), dest);
// Finalize (also frees memory — do not use handleRef after this)
lib.finalize(handleRef);| File | Purpose |
|---|---|
interop/bmi.h |
C header with exact exported symbol signatures and ABI notes (DIFF 1–8) |
interop/FortranModelJnaLibrary.java |
JNA interface (bmi.model package) |
interop/FortranString.java |
Helper: converts Java String ↔ Fortran fixed-size 2048-byte buffer |
interop/bmi_from_spec.h |
Reference header derived from the abstract Fortran spec (not the actual ABI) |
See interop/bmi.h for important ABI differences from the abstract spec,
including known stub implementations and a bug in get_grid_edge_nodes.
get_value_ptr_*— not implemented; always returnsBMI_FAILURE.get/set_value_at_indices_*— not implemented; always returnsBMI_FAILURE.get_grid_edge_nodes— contains a logic bug iniso_c_bmif_2_0.f90(line 893) that may cause incorrect array sizing. Seeinterop/bmi.h(BUG 1).
- NOAA-OWP
iso_c_fortran_bmiinterop layer: Nels Frazier, NOAA OWP (Apache 2.0)