管理库(静态和动态库)#

如果您需要管理一个由数十个源文件构建的程序(这并不少见!),那么指定所有目标文件的命令行将非常长。这很快就会变得乏味甚至无法维护。因此,需要一个不同的解决方案:创建您自己的库。

库以紧凑的形式包含任意数量的目标文件,从而使命令行变得更短。

$ gfortran -o tabulate tabulate.f90 functions.o supportlib.a

其中“supportlib.a”是包含一个、两个或多个目标文件的集合,所有这些文件都已编译并放入库中。扩展名“.a”由 Linux 和类 Linux 平台使用。在 Windows 上,使用扩展名“.lib”。

创建您自己的库并不复杂:在 Linux 上,您可以使用像ar这样的实用程序来实现此目的。

$ gfortran -c file1.f90 file2.f90
$ gfortran -c file3.f90 ...
$ ar r supportlib.a file1.o file2.o
$ ar r supportlib.a file3.o ...

或在 Windows 上使用lib实用程序。

c:\...> ifort -c file1.f90 file2.f90
c:\...> ifort -c file3.f90 ...
c:\...> lib /out:supportlib.lib file1.obj file2.obj
c:\...> lib supportlib.lib file3.obj ...

注意

  • 命令ar带选项r要么创建库(名称出现在选项之后),要么将新的目标文件添加到库中(或替换任何现有的目标文件)。

  • 如果您使用选项/out:并在其旁边指定新库的名称,则命令lib将创建一个新库。要将目标文件添加到现有库中,请省略/out:部分。

  • 在 Linux 等平台上,有一种命名库的特定约定。如果您将库命名为“libname.a”(请注意“lib”前缀),那么您可以在链接步骤中将其称为-lname

  • 库通常在由选项-L/LIBPATH指示的目录中搜索。这使您不必为每个库都指定确切的路径。

使用库,您可以构建非常大的程序,而无需使用极长的命令行。

静态库与动态库#

以上讨论默认假设您正在使用所谓的静态库。静态库(或至少其内容的一部分)成为可执行程序的组成部分。更改程序中包含的例程的唯一方法是使用库的新版本重新构建程序。

一种灵活的替代方法是使用所谓的动态库。这些库保留在可执行程序之外,因此可以在不重新构建整个程序的情况下替换它们。编译器乃至操作系统本身都严重依赖这种动态库。您可以将动态库视为一种需要一些帮助才能运行的可执行程序。

构建动态库的方式与构建静态库略有不同:您使用编译器/链接器而不是像arlib这样的工具。

在 Linux 上

$ gfortran -fpic -c file1.f90 file2.f90
$ gfortran -fpic -c file3.f90 ...
$ gfortran -shared -o supportlib.so file1.o file2.o file3.o ...

在 Windows 上,使用 Intel Fortran 编译器

$ ifort -c file1.f90 file2.f90
$ ifort -c file3.f90 ...
$ ifort -dll -exe:supportlib.dll file1.obj file2.obj file3.obj ...

区别在于

  • 您需要在 Linux 上指定一个编译选项,对于 gfortran 来说是-fpic,因为目标代码略有不同。

  • 您需要在链接步骤中说明您想要一个动态库(在 Linux 上:一个共享对象/库,因此扩展名为“.so”;在 Windows 上:一个动态链接库)。

还有一件事需要注意:在 Windows 上,您必须显式指定某个过程要导出,即在动态库中可见。有多种方法(取决于您使用的编译器)可以实现此目的。一种方法是通过所谓的编译器指令

subroutine myroutine( ... )
!GCC$ ATTRIBUTES DLLEXPORT:: myroutine

或者,使用 Intel Fortran 编译器

subroutine myroutine( ... )
!DEC$ ATTRIBUTES DLLEXPORT:: myroutine

除了动态库 (DLL) 之外,还可以生成所谓的导入库。

由于细节因编译器而异,这里有两个示例:Cygwin 上的 gfortran 和 Windows 上的 Intel Fortran。在这两种情况下,我们都查看文件“tabulate.f90”中的tabulate程序。

GNU/Linux 和 gfortran#

tabulate程序需要一个用户定义的例程f。如果我们将其驻留在动态库(例如“functions.dll”)中,我们可以简单地通过将另一个动态库放在目录中来替换函数的实现。无需像这样重新构建程序。

在 Cygwin 上,无需显式导出过程——当您构建动态库时,所有公开可见的例程都会导出。此外,不会生成导入库。

由于我们的动态库可以从单个源文件构建,因此我们可以采取捷径

$ gfortran -shared -o functions.dll functions.f90

这将生成文件“functions.dll”和“user_functions.mod”。实用程序nm告诉我们函数f的确切名称

$ nm functions.dll
...
000000054f9d7000 B __dynamically_loaded
                 U __end__
0000000000000200 A __file_alignment__
000000054f9d1030 T __function_MOD_f
000000054f9d1020 T __gcc_deregister_frame
000000054f9d1000 T __gcc_register_frame
...

它已收到前缀__function_MOD_以将其与可能在另一个模块中定义的任何其他例程“f”区分开来。

下一步是构建程序

$ gfortran -o tabulate tabulate.f90 functions.dll

DLL 和 .mod 文件用于构建可执行程序,并检查函数的接口、正确的名称以及对名为“functions.dll”的“a”DLL 的引用。

您可以用另一个实现不同函数“f”的共享库“functions.dll”替换它。当然,您需要小心使用此函数的正确接口。编译器/链接器不再被调用,因此它们无法进行检查。

Windows 和 Intel Fortran#

设置与 Linux 相同,但在 Windows 上,必须显式导出例程。并且会生成导入库——这是链接步骤中应使用的库。

源文件必须包含编译器指令,否则函数f不会导出

real function f( x )
!DEC$ ATTRIBUTES DLLEXPORT :: f

我们再次采取捷径

$ ifort -exe:functions.dll functions.f90 -dll

这将生成文件“functions.dll”、“user_functions.mod”以及“functions.lib”(以及此处无关紧要的其他两个文件)。“依赖项查看器”程序告诉我们函数“f”的确切名称是FUNCTION_mp_F。它也已导出,以便链接器可以在下一步中找到它

$ ifort tabulate.f90 functions.lib

请注意,我们需要指定导出库的名称,而不是 DLL!

(另请注意:Intel Fortran 编译器使用第一个源文件的名称作为可执行文件的名称——这里我们无需-exe选项。)

与 Cygwin 一样,DLL 和 .mod 文件用于构建可执行程序,并检查函数的接口、正确的名称以及对名为“functions.dll”的“a”DLL 的引用。

您可以用另一个共享库“functions.dll”替换它,但需要同样的谨慎:虽然实现可以完全不同,但函数的接口必须相同。