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
Tip: Here, RK
is an alias to (or basically, an abbreviation for) the real kind real64
which is imported from the Fortran standard’s intrinsic module iso_fortran_env
. It is then used to declare the kinds of the real input number and the output result. The same is true for IK
and int32
.
Making this Fortran function callable form other languages requires two major revisions in the code,
- 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, - making the function interface consistent with its interface in the other languages.
Important: In Fortran, interoperation with other programming languages is provided via interoperation with the C programming language. In other words, any language that can interoperate with C, can also interoperate with 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.
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,
- a DLL file named
Power_mod.dll
containing the library’s executable code and, - 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
Tip: For more information on how to create DLLs with Intel Fortran compiler, see this page.
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
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.
Tip: Since C passes arguments to functions always by value, the value
attribute will have to be added to the interface variable declarations in the Fortran function. Alternatively, one could keep the Fortran function’s argument list intact, but ensure that all the input values to function at the time of call, are passed by reference.
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
Warning: Note that the presence of the DLLEXPORT
comment line in the Fortran module is essential for successful export of the function to the DLL file in Windows Operating System via Intel Fortran compiler. Also, notice that the binding name is the global identifier for the function.
Important: Notice that the function prototype in Python uses ct.POINTER
(with POINTER all in upper-case) to declare the input arguments as pointer, and then the arguments are passed as pointers via ct.byref
. This could have also been done via ct.pointer
. However, ct.pointer
does a lot more than simply creating a reference for the purpose of communication and therefore, can be significantly slower than ct.byref
.