管理库(静态和动态库)#
如果你需要管理一个由几十个源文件构建的程序(这并不少见!),指定所有目标文件所需的命令行将会非常长。这很快就变得乏味,甚至不可能维护。因此有一个不同的解决方案:创建自己的库。
库以紧凑的形式包含任意数量的目标文件,因此命令行变得更短:
$ 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 ...
注意:
带有选项
r
的ar
命令要么创建库(名称出现在选项之后),要么将新的目标文件添加到库中(或替换任何现有的)。如果你使用选项
/out:
并在其旁边指定新库的名称,命令lib
将创建一个新库。要将对象文件添加到现有库中,请省略/out:
。在像 Linux 这样的平台上,命名库有一个特定的约定。如果你将库命名为 “libname.a”(注意 “lib” 前缀),那么你可以在链接步骤中将其称为
-lname
。库通常在由选项
-L
或/LIBPATH
指示的目录中查找。这使你不必为每个库指定确切的路径。
使用库,你可以构建非常大的程序,而无需求助于极长的命令行。
静态库与动态库#
上面的讨论默认你使用的是所谓的静态库。静态库(或至少部分内容)成为可执行程序的组成部分。更改程序中包含的例程的唯一方法是使用新版本的库重新构建程序。
一种灵活的替代方法是使用所谓的动态库。这些库保留在可执行程序之外,因此可以在不重建整个程序的情况下进行替换。编译器甚至操作系统本身都严重依赖于这样的动态库。你可以将动态库视为一种需要一些帮助才能运行的可执行程序。
构建动态库与构建静态库的工作方式略有不同:你使用编译器/链接器而不是像 ar
或 lib
这样的工具。
在 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”替换为另一个,但同样需要注意:虽然实现可能完全不同,但函数的接口必须相同。