Управление библиотеками (статические и динамические библиотеки)#

Если вам нужно управлять программой, собранной из десятков файлов исходного кода (а это не редкость!), то командная строка, необходимая для указания всех объектных файлов будет очень длинной. Очень скоро поддержка такого списка станет утомительной или даже невозможной. Поэтому необходимо другое решение: создание собственных библиотек.

Библиотеки содержат любое количество объектных файлов в компактной форме, так что командная строка для сборки программы становится короче:

$ 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 либо создаёт библиотеку (имя указывается после опции), либо добавляет в библиотеку новые объектные файлы (или заменяет все существующие).

  • Команда lib создаёт новую библиотеку, если указать опцию /out: с именем новой библиотеки следом за ней. Чтобы добавить объектные файлы к существующей библиотеке, опустите опцию /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 с компилятором 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: shared object/library, отсюда расширение «.so»; в Windows: dynamic link library)

Есть ещё один момент, о котором следует знать: в Windows вы должны явно указать, что процедура должна быть экспортирована, т.е. видна в динамической библиотеке. Существует несколько способов, в зависимости от используемого компилятора, добиться этого. Один из них – с помощью так называемой директивы компилятора:

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

Или, при использовании компилятора Intel Fortran:

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

Кроме динамической библиотеки (DLL), может быть сгенерирована так называемая импортируемая библиотека (import library).

Поскольку детали отличаются в зависимости от компилятора, приведём два примера: gfortran в оболочке Cygwin и Intel Fortran в Windows. В обоих случаях мы рассматриваем программу tabulate из файла «tabulate.f90».

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 используются для сборки исполняемого файла программы с проверкой интерфейса функции, правильного имени и ссылки на DLL, названой «functions.dll».

Вы можете заменить разделяемую библиотеку «functions.dll» на другую, реализующую другую функцию «f». Конечно, нужно быть осторожным, чтобы использовать правильный интерфейс для этой функции. Компилятор и компоновщик не вызываются повторно, поэтому они не могут сделать никаких проверок.

Windows и Intel Fortran#

Настройка в этом случае такая же, как и в случае с Linux, но в Windows необходимо явно экспортировать подпрограммы. При этом создаётся импортируемая библиотека (import library) – именно эта библиотека должна использоваться на этапе компоновки.

Файл исходного кода должен содержать директиву компилятора, иначе функция 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!

(Отметим также: компилятор Intel Fortran использует имя первого файла исходного файла в качестве имени исполняемого файла программы – здесь мы обходимся без использования опции -exe.)

Как и в оболочке Cygwin, файлы DLL и .mod используются для сборки исполняемого файла программы с проверкой интерфейса функции, правильного имени и ссылки на DLL, названную «functions.dll».

Вы можете заменить разделяемую библиотеку «functions.dll» другой, но при этом необходимо соблюдать ту же осторожность: хотя реализация может быть совершенно другой, интерфейс функции должен быть тем же самым.