If you are doing heavy scientific computing, you will eventually encounter Fortran (BLAS, LAPACK).
Binding Fortran can be tricky because:
- Name Mangling: Compilers often lowercase names and append underscores (e.g.,
DGEMMbecomesdgemm_). - Pass-by-Reference: Historically, Fortran passes everything by reference (pointers).
- Strings: Legacy Fortran expects string pointers to be followed by hidden length arguments at the end of the argument list.
However, modern Fortran (2003+) offers the iso_c_binding module, which allows us to define a standard C ABI.
The Recipe
This time, we'll compile a Fortran subroutine that adds two numbers, and another that prints a string based on an explicit length argument.
use v5.40;
use Affix qw[:all];
use Affix::Build;
$|++;
# 1. Compile Fortran
# Requires a Fortran compiler be installed
my $c = Affix::Build->new( name => 'fortran_demo' );
$c->add( \<<~'END', lang => 'f90' );
! Numeric subroutine: Passes pointers by default
subroutine f_add(a, b, res) bind(c, name='f_add')
use iso_c_binding
integer(c_int), intent(in) :: a, b
integer(c_int), intent(out) :: res
res = a + b
end subroutine
! String subroutine: Explicit length handling
subroutine f_hello(msg, len) bind(c, name='f_hello')
use iso_c_binding
character(kind=c_char, len=1), intent(in) :: msg(*)
integer(c_int), value :: len
! Print the slice using the passed length
print *, "Fortran says: ", msg(1:len)
end subroutine
END
my $lib = $c->link;
# 2. Bind
# Modern Fortran with bind(c) respects C names.
# However, Fortran arguments are usually pointers.
affix $lib, 'f_add', [ Pointer [Int], Pointer [Int], Pointer [Int] ] => Void;
# For strings, we pass the buffer (String) and the length (Int) explicitly.
affix $lib, 'f_hello', [ String, Int ] => Void;
# 3. Call (Math)
# We must pass references (\$) to our numbers because Fortran expects pointers.
my $a = 10;
my $b = 20;
my $res = 0;
f_add( \$a, \$b, \$res );
say "10 + 20 = $res"; # 30
# 4. Call (Strings)
my $msg = 'Hello World';
# We pass the string string (char*), AND the length manually.
f_hello( $msg, length($msg) );
How It Works
-
1.
iso_c_bindingThis standard module is the key to sanity. By addingbind(c, name='f_add'), we force the Fortran compiler to generate a symbol namedf_add(no trailing underscores) that uses the standard C calling convention. -
2. Pointers vs Values In
f_add, the argumentsaandbdo not have thevalueattribute. This means Fortran expects memory addresses. In Perl, we handle this by passing references (\$a). Inf_hello,lenis defined withvalue. This means Fortran expects a raw integer, so we pass it directly from Perl.
Kitchen Reminders
-
Runtime Dependencies Shared libraries compiled with
gfortrandepend onlibgfortran. On Linux, this is usually automatic. On Windows (MinGW), if you ship your compiled DLL to another machine, you must ensurelibgfortran-*.dllis also in the PATH, or the library will fail to load. -
Hidden Arguments If you are binding to legacy Fortran (BLAS/LAPACK) that was not written with
iso_c_binding, you must account for "Hidden Arguments." For every string argument in the signature, you must usually append aSize_targument to the very end of the argument list containing the string length.