在过去的10年里,我一直在用新的Fortran标准开发一个用于计算热力学的免费代码——它运行得很好,但它很大,有超过60000行代码和大约500个子例程。该软件使用从评估数据库。
最近我被要求提供一个加密的数据库软件,我同意尝试这样做。实际的加密是由一个独立的供应商完成,该供应商还将为库提供解密数据库的例程。然而,我有义务防止任何人从我的软件中提取数据库参数,因此我必须提供一个小的预编译库和解密库,以存储和计算解密后的模型参数,从而防止程序员从软件中提取模型参数。
这项任务需要对我的数据结构进行重大重组,并在一个特殊的库中提取一些子程序,这些子程序将不属于自由软件的一部分。我已经设法修改了数据结构,并提取了一小套子例程,用于使用解密的参数进行计算。然而,当从加密数据库中读取模型参数时,我必须找到一个地方来存储这些与用户选择的元素和阶段相关的参数。找到这个地方的子程序在我想要保持自由和开发的程序中。因此,在读取库模块中的加密数据库时,我必须访问自由软件中的大量子例程。
我将尝试使用图1以简单的方式解释我的问题,其中主程序调用模块MODA内的子程序SUBB。SUBB调用SUBC,SUBC在返回之前调用SUBC。这是我原始代码的工作方式,这没有问题,因为所有子例程都在同一个模块内。
图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
假设SUBA是读取加密数据库的子程序以秘密方式存储所述模型参数。我在图2中把它做成了一个单独的模块(和库),它不会成为免费软件的一部分。该库将独立于主程序进行编译。
图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
在图3中,我分离出模块MODB(当我试图"使用"SUBA时)和图4中的主程序。
图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
图4
program main
use modb
use secret
integer if
if=0
call subb(ie)
write(*,*)'Value: ',if
end program main
我所有将其连接在一起的尝试都失败了,无论是通过编译当I";使用";不同的模块;SUBC_";是未定义的引用。我花了几个星期的时间来处理数据结构问题,我认为子程序调用会更容易。欢迎任何提示。
我理解";使用";两个模块之间可能是非法的,而且无论如何都有问题,但我不知道如何处理。我不能把整个MODB模块作为SECRET模块的一部分,因为我想让它尽可能小。代码仍在开发中,我无法维护单独的部分。
但也许有一个优雅的解决方案。我理解这一点";仅:";试图解决这个问题。也许有一些";接口";或"外部";我不知道的功能。
对于开发,我在Windows上使用来自MSYS2的gfortran、gcc 10.2.0,但我的软件在各种Linux机器和MacOS上使用。当前版本可在github上的sundmanbo/openalphad存储库中获得。
您的secret
模块依赖于模块modb
,后者依赖于secret
模块中的对象。这是一个循环依赖。它可以很容易地通过Fortran 2008submodule
s来解决。该思想的本质是将模块过程的接口与其实现分离。您必须将模块中的接口放在一个文件中,将submodule
中的实现放在单独的文件中。为了避免编译错误,您必须首先编译模块文件(包含接口),然后再编译实现(像CMake这样的构建生成器会自动检测依赖项并为您执行此操作)。以下是一个示例实现,
secret_mod.F90
:的内容
module secret
implicit none
interface
module subroutine suba(ib)
integer, intent(out) :: ib
end subroutine
end interface
end module secret
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
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
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
关键是只有在子模块中才有依赖关系(而不是在模块中)。现在按照模块/子模块顺序进行编译,
gfortran secret_mod.F90 modb_mod.F90 secret_mod@Routines_smod.F90 modb_mod@Routines_smod.F90 main.F90 -O
./a.out
Value: 0
如果我理解正确,您可以使用子模块来解决循环依赖关系。子模块允许您将过程接口与过程实现分离。
在您的代码中,只有suba
的实现需要了解subc
。因此,通过将suba
分为一个模块和一个子模块,将对modb
的依赖从模块转移到子模块。
使用子模块,您的代码看起来像
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