'Calling subroutines in different Fortran modules
I have been developing a free code for computational thermodynamics in the new Fortran standard for the last 10 years it it works quite well but it is large, more than 60000 lines of code and some 500 subroutines. The software calculates equilibria, phase diagrams and simulates phase transformations using model parameters read from assessed databases.
Recently I was asked to provide an encrypted database with this software and I agreed to try to do that. The actual encryption is done by an independent vendor which will also provide a library with routines to decrypt the database. However, I am obliged to prevent anyone extracting the database parameters from inside my software so I have to provide a small pre-compiled library together with the decryption library to store and calculate with the decrypted model parameters in a way that prevents a programmer to extract the model parameters from my software.
This task required a major reorganizing of my data structures and to extract some subroutines in a special library which will not be part of the free software. I have managed to modify the data structure and I have extracted a small set of subroutines for the calculations with the decrypted parameters. However, when reading the model parameters from the encrypted database I must find a place to store these related to the elements and phases selected by the user. The subroutines to find this place are in the program I want to keep free and develop. So while reading the encrypted database in the library module I must access a large number of subroutines in the free software.
I will try to explain my problem in a simple way using figure 1 where a main program calls subroutine SUBB inside module MODA. SUBB calls SUBC which calls SUBC before returning. This is the way my original code works and it is no problem because all subroutines are inside the same module.
figure 1
module moda
contains
subroutine suba(ib)
integer :: ib
write(*,*)'suba 1:',ib
ib=5
call subc(ib)
write(*,*)'suba 2:',ib
end subroutine suba
!
subroutine subb(id)
integer :: id
write(*,*)'subb 1:',id
call suba(id)
write(*,*)'subb 2:',id
end subroutine subb
!
subroutine subc(ie)
integer :: ie
write(*,*)'subc 1:',ie
ie=2
write(*,*)'subc 2:',ie
end subroutine subc
end module moda
program main
use moda
integer if
if=0
call subb(if)
write(*,*)'Value: ',if
end program main
Assume SUBA is the subroutine reading the encrypted database and storing the model parameters in a secret way. I have made this into a separate module (and library) in figure 2 which will not be part of the free software. The library will be compiled independently of the main program.
figure 2
module secret
! use modb, only: subc creates compilation error
contains
subroutine suba(ib)
integer ib
ib=5
call subc(ib)
end subroutine suba
end module secret
In figure 3 I separated out the module MODB (as I tried to "use" that for SUBA) and in figure 4 the main program.
Figure 3
module modb
! use secret, only: suba creates compilation error
contains
subroutine subb(id)
integer :: id
call suba(id)
end subroutine subb
subroutine subc(ie)
integer :: ie
ie=2
end subroutine subc
end module modb
Figure 4
program main
use modb
use secret
integer if
if=0
call subb(ie)
write(*,*)'Value: ',if
end program main
All my attempts to link this together fails, either by compilation errors when I "use" the different modules or that "SUBC_" is an undefined reference. I have spent a few weeks to handle the data structure problems, I thought the subroutine calls would be easier. Any hint is welcome.
I understand that "use" between two modules is probably illegal and anyway problematic but I have no idea how to handle this. I can not make the whole of MODB module part of the SECRET module because I want to keep it as small as possible. The code is still developing and I cannot maintain separate parts.
But maybe there is an elegant solution. I understood this "only:" trying to solve this problem. Maybe there is some "interface" or "external" feature I do not know about.
For development I use gfortran, gcc 10.2.0 from MSYS2 on Windows but my software is used on various Linux machines and MacOS. The current version is available on sundmanbo/opencalphad repository on github.
Solution 1:[1]
Your secret module depends on module modb which depends on objects from the secret module. That is a circular dependency. It can be readily resolved via Fortran 2008 submodules. The essence of the idea is to separate the interfaces of module procedures from their implementation. You would have to put the interfaces in the module in one file and the implementations in a submodule in a separate file. To avoid the compilation errors, you would have to then compile the module files (containing the interfaces) first, followed by the implementations (build generators like CMake would automatically detect the dependencies and do this for you). Here is an example implementation,
Contents of secret_mod.F90:
module secret
implicit none
interface
module subroutine suba(ib)
integer, intent(out) :: ib
end subroutine
end interface
end module secret
Contents of secret_mod@Routines_smod.F90:
submodule (secret) Routines_smod
contains
module procedure suba
use modb, only: subc
ib = 5
call subc(ib)
end procedure
end submodule Routines_smod
Contents of modb_mod.F90:
module modb
implicit none
interface
module subroutine subb(id)
integer, intent(out) :: id
end subroutine
module subroutine subc(ie)
integer, intent(out) :: ie
end subroutine
end interface
end module modb
Contents of modb_mod@Routines_smod.F90:
submodule (modb) Routines_smod
contains
module procedure subc
ie = 2
end procedure subc
module procedure subb
use secret, only: suba
call suba(id)
end procedure subb
end submodule Routines_smod
The key is have the dependencies only in the submodules (not in the modules). Now compile while respecting the module/submodule order,
gfortran secret_mod.F90 modb_mod.F90 secret_mod@Routines_smod.F90 modb_mod@Routines_smod.F90 main.F90 -O
./a.out
Value: 0
Solution 2:[2]
If I understand you correctly, you can resolve your circular dependency using a submodule. Submodules allow you to separate the procedure interfaces from the procedure implementations.
In your code, only the implmentation of suba needs to know about subc. So by separating suba into a module and a submodule, this moves the dependency on modb from the module to the submodule.
Using a submodule, your code would look like
module secret
interface
module subroutine suba(ib)
integer ib
end subroutine suba
end interface
end module secret
submodule (secret) secret_submodule
use modb, only: subc
contains
subroutine suba(ib)
integer ib
ib=5
call subc(ib)
end subroutine suba
end submodule
module modb
use secret, only: suba
contains
subroutine subb(id)
integer :: id
call suba(id)
end subroutine subb
subroutine subc(ie)
integer :: ie
ie=2
end subroutine subc
end module modb
program main
use modb
use secret
integer if
if=0
call subb(ie)
write(*,*)'Value: ',if
end program main
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Scientist |
| Solution 2 | veryreverie |
