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

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

库以紧凑的形式包含任意数量的目标文件,因此命令行变得更短:

$ 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 ...

注意:

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

  • 如果你使用选项 /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 上,使用英特尔 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 上,你必须明确指定要 exported 的过程,即在动态库中可见。有几种方法 —— 取决于你使用的编译器 —— 来实现这一点。一种方法是通过所谓的编译器指令:

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

或者,使用英特尔 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 文件用于构建可执行程序,检查函数的接口、正确的名称和对“a”DLL(称为“functions.dll”)的引用。

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

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”(以及这里不重要的另外两个文件)。 “dependency walker”程序告诉我们函数“f”的确切名称是 FUNCTION_mp_F。它也被导出,以便在下一步中链接器可以找到它:

$ ifort tabulate.f90 functions.lib

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

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

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

你可以将共享库“functions.dll”替换为另一个,但同样需要注意:虽然实现可能完全不同,但函数的接口必须相同。