Consider the following example Fortran function contained in Fortran module Power_mod which takes an input real number base of type real64 and an integer variable expo of type int32 and returns $\mathrm{base}^{\mathrm{expo}}$ as output,

module Power_mod
    implicit none
contains
    function getPower(base,expo) result(power)
        use, intrinsic :: iso_fortran_env, only: RK => real64, IK => int32
        real(RK)    , intent(in)    :: base
        integer(IK) , intent(in)    :: expo
        real(RK)                    :: power
        power = base ** expo
    end function getPower
end module Power_mod

Making this Fortran function callable form other languages requires two major revisions in the code,

  1. telling the Fortran compiler to not perform name mangling for this function, leaving its name to getPower() or some other global identifier specified within the program,
  2. making the function interface consistent with its interface in the other languages.

Problem Part 1: interoperable DLL

Fortran

Modify this function and provide the Fortran Compiler command options for the compiler of your own choice with the required flags to generate a Fortran DLL file that can be called from within Fortran programs as well as within other programming languages.

Solution Part 1: interoperable DLL

Fortran

Here is a solution based on the Intel Fortran compiler in Windows environment,

module Power_mod
    implicit none
contains
    function getPower(base,expo) result(power) bind(C, name="getPow")
        !DEC$ ATTRIBUTES DLLEXPORT :: getPower
        use, intrinsic :: iso_c_binding, only: RK => c_double, IK => c_int32_t
        real(RK)    , intent(in)    :: base
        integer(IK) , intent(in)    :: expo
        real(RK)                    :: power
        power = base ** expo
    end function getPower
end module Power_mod

The exclamation mark in !DEC$ simply converts the entire line to a Fortran comment so that this line can only be parsed by the Fortran compiler when needed. The rest, DEC$ tells the Intel compiler to parse this line and export the function named getPower to the DLL file. The name that appears after ATTRIBUTES DLLEXPORT :: must be the exact name of the function that is being exported.

By default, the DLL export comment line will be ignored by all Fortran compilers, including Intel’s. However, the DLL comment line is recognizable by the Intel Fortran compiler only if the ifort command is invoked with the following optional flags,

ifort /dll Power_mod.f90
Intel(R) Visual Fortran Intel(R) 64 Compiler for applications running on Intel(R) 64, Version 19.0.4.245 Build 20190417
Copyright (C) 1985-2019 Intel Corporation.  All rights reserved.

Microsoft (R) Incremental Linker Version 14.22.27905.0
Copyright (C) Microsoft Corporation.  All rights reserved.

-out:Power_mod.dll
-dll
-implib:Power_mod.lib
Power_mod.obj
   Creating library Power_mod.lib and object Power_mod.exp

where Power_mod.f90 is the name of the file containing the Power_mod module. This command creates the following files,

  1. a DLL file named Power_mod.dll containing the library’s executable code and,
  2. an import library, Power_mod.lib which the linker uses to associate a main program with the DLL, and which one must link with applications that call the DLL.

If you also specify /exe:filename, the filename you specify will be used instead of the default DLL name,

ifort /dll Power_mod.f90 /exe:Power
Intel(R) Visual Fortran Intel(R) 64 Compiler for applications running on Intel(R) 64, Version 19.0.4.245 Build 20190417
Copyright (C) 1985-2019 Intel Corporation.  All rights reserved.

Microsoft (R) Incremental Linker Version 14.22.27905.0
Copyright (C) Microsoft Corporation.  All rights reserved.

-out:Power.dll
-dll
-implib:Power.lib
Power_mod.obj
   Creating library Power.lib and object Power.exp

To see the contents of the generated library file, one can use the Windows command,

dumpbin -exports Power.lib
Microsoft (R) COFF/PE Dumper Version 14.22.27905.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file Power.lib

File Type: LIBRARY

     Exports

       ordinal    name

                  getPow

  Summary

          B7 .debug$S
          14 .idata$2
          14 .idata$3
           8 .idata$4
           8 .idata$5
           8 .idata$6

or,

dumpbin /symbols Power.exp
Microsoft (R) COFF/PE Dumper Version 14.22.27905.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file Power.exp

File Type: COFF OBJECT

COFF SYMBOL TABLE
000 01006D01 ABS    notype       Static       | @comp.id
001 00000810 ABS    notype       Static       | @feat.00
002 00000000 SECT1  notype       Static       | .edata
003 00000000 SECT2  notype       Static       | .debug$S
004 00000032 SECT1  notype       Static       | szName
005 00000028 SECT1  notype       Static       | rgpv
006 0000002C SECT1  notype       Static       | rgszName
007 00000030 SECT1  notype       Static       | rgwOrd
008 0000003A SECT1  notype       Static       | $N00001
009 00000000 UNDEF  notype       External     | getPow

String Table Size = 0x0 bytes

  Summary

         129 .debug$S
          41 .edata

Here is an example Fortran program that uses the DLL file,

program Power_prog
    use, intrinsic :: iso_c_binding, only: RK => c_double, IK => c_int32_t
    use Power_mod, only: getPower
    implicit none
    real(RK)    :: base = 100._RK
    integer(IK) :: expo = 3_IK
    interface
    function getPow(base,expo) result(power)
        !DEC$ ATTRIBUTES DLLIMPORT, DECORATE, ALIAS: 'getPow' :: getPow
        use, intrinsic :: iso_c_binding, only: RK => c_double, IK => c_int32_t
        real(RK)    , intent(in)    :: base
        integer(IK) , intent(in)    :: expo
        real(RK)                    :: power
    end function getPow
    end interface
    write(*,"(*(g0.13,:,' '))") "getPower(", base, ",", expo, ") =", getPower(base,expo)
    write(*,"(*(g0.13,:,' '))") "  getPow(", base, ",", expo, ") =",   getPow(base,expo)
end program Power_prog

Compile this program Pow_prog.f90 with the following Intel ifort command to get an executable with a default name Pow_prog.exe,

ifort Power_prog.f90 Power.lib -o main.exe
Intel(R) Visual Fortran Intel(R) 64 Compiler for applications running on Intel(R) 64, Version 19.0.4.245 Build 20190417
Copyright (C) 1985-2019 Intel Corporation.  All rights reserved.

Microsoft (R) Incremental Linker Version 14.22.27905.0
Copyright (C) Microsoft Corporation.  All rights reserved.

-out:main.exe
-subsystem:console
Power_prog.obj
Power.lib

Note that Power.lib has to appear as the last input argument to ifort. Now running the executable yields,

main.exe
getPower( 100.0000000000 , 3 ) = 1000000.000000
  getPow( 100.0000000000 , 3 ) = 1000000.000000

Problem Part 2: calling Fortran DLL from Python

Python

Write a Python main program that calls the Fortran DLL’s function and computes $100.0^3$. Use ctypes module in Python for this purpose.

Solution Part 2: calling Fortran DLL from Python

Python

The generated DLL in the previous section is already portable and callable from Python, however, a Python wrapper has to be carefully written for it in order to successfullly call it. Here is a try,

! Fortran module and function to be called from Python
module Power_mod
    implicit none
contains
    function getPower(base,expo) result(power) bind(C, name="getPow")
        !DEC$ ATTRIBUTES DLLEXPORT :: getPower
        use, intrinsic :: iso_c_binding, only: RK => c_double, IK => c_int32_t
        real(RK)    , intent(in)    :: base
        integer(IK) , intent(in)    :: expo
        real(RK)                    :: power
        power = base ** expo
    end function getPower
end module Power_mod
ifort /dll Power_mod.f90
Intel(R) Visual Fortran Intel(R) 64 Compiler for applications running on Intel(R) 64, Version 19.0.4.245 Build 20190417
Copyright (C) 1985-2019 Intel Corporation.  All rights reserved.

Microsoft (R) Incremental Linker Version 14.22.27905.0
Copyright (C) Microsoft Corporation.  All rights reserved.

-out:Power_mod.dll
-dll
-implib:Power_mod.lib
Power_mod.obj
   Creating library Power_mod.lib and object Power_mod.exp
# Python script that calls the Fortran function
import ctypes as ct
import numpy as np

# import the dll
flib = ct.CDLL('Power.dll')

# define input variables with the correct types
base = ct.c_double(100.0)
expo = ct.c_int32(3)

# declare the DLL function's result type
flib.getPow.restype = ct.c_double

# declare DLL function's argument types to avoid wrong input arguments to the function
flib.getPow.argtypes = ( ct.POINTER(ct.c_double) , ct.POINTER(ct.c_int32) )

# call the DLL function
result = flib.getPow( ct.byref(base)
                    , ct.byref(expo)
                    )
print( "getPow({},{}) = {}".format(base,expo,result) )
getPow(c_double(100.0),c_long(3)) = 1000000.0

Comments