Zarządzanie bibliotekami (statyczne i dynamiczne biblioteki)#

Jeśli potrzebujesz zarządzać programem złożonym z dziesiątek plików źródłowych (a nie jest to rzadkie!), komendy potrzebne do sprecyzowania wszystkich plików obiektowych będą bardzo długie. To szybko staje się żmudne lub nawet niewykonalne. Potrzebne jest więc inne rozwiązanie: stworzenie własnych bibliotek.

Biblioteki zawierają pewną ilość plików obiektowych w kompaktowej formie, tak że komendy stają się o wiele krótsze:

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

„supportlib.a” jest kolekcją jednej, dwóch lub wielu plików obiektowych, wszystkich skompilowanych i dodanych do biblioteki. Rozszerzenie „.a” jest używane na systemie operacyjnym Linux i innych platformach stworzonych na jego wzór. Na Windowsie używane jest rozszerzenie „.lib”.

Tworzenie własnych bibliotek nie jest takie skomplikowane: na Linuksie, możesz tego dokonać za pomocą funkcji takich jak 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 ...

lub na Windowsie używając funkcji 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 ...

Uwaga:

  • Komenda ar razem z opcją r albo tworzy bibliotekę (nazwa powinna być po opcji) albo dodaje nowy plik obiektowy do biblioteki (lub zastępuje już istniejące).

  • Komenda lib stworzy nową bibliotekę jeśli określisz opcję /out: z nazwą nowej biblioteki zaraz po niej. Aby dodać plik obiektowy do istniejącej już biblioteki, pomiń część /out:.

  • Na platformach takich jak Linux istnieje szczegółowa praktyka nazywania bibliotek. Jeśli nazwiesz swoją bibliotekę „libname.a” (zwróć uwagę na prefiks „lib”), możesz wtedy odnosić się do niej jako -lname podczas procesu łączenia.

  • Biblioteki znajdują się zazwyczaj w folderach określonych przez opcję -L lub /LIBPATH. Dzięki temu nie musisz za każdym razem precyzować dokładnej ścieżki dla każdej biblioteki.

Używanie własnych bibliotek do bardzo dużych programów może uratować cię przed używaniem bardzo długich komend.

Statyczne kontra dynamiczne biblioteki#

Powyższa dyskusja zakłada, że używasz tak zwanych bibliotek statycznych. Statyczne biblioteki (lub przynajmniej części ich zawartości) stają się nieodłączną częścią wykonywalnego programu. Jedynym sposobem na zmienienie tych wkomponowanych procedur jest przez stworzenie programu na nowo z użyciem nowej wersji danej biblioteki.

Elastyczną alternatywą jest użycie tak zwanych dynamicznych bibliotek. Te biblioteki znajdują się na zewnątrz wykonywalnego programu i wskutek tego mogą być zastąpione bez konieczności tworzenia całego programu od nowa. Kompilatory oraz same systemy operacyjne w znacznym stopniu zależą od dynamicznych bibliotek. Możesz myśleć o dynamicznych bibliotekach jak o rodzaju wykonywalnych programów, które potrzebują trochę pomocy, aby mogły zostać uruchomione.

Tworzenie dynamicznych bibliotek działa trochę inaczej niż tworzenie bibliotek statycznych: należy użyć kompilatora/programu łączącego zamiast funkcji takich jak ar lub lib.

Na a Linuksie:

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

Na Windowsie, z kompilatorem Intel Fortran:

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

Różnice polegają na tym, że:

  • Musisz określić opcje kompilacji na Linuksie, dla gfortran jest to -fpic, ponieważ kod obiektowy troszkę się różni.

  • Musisz określić w programie łączącym, że chcesz stworzyć dynamiczną bibliotekę (na Linuksie: bibliotekę/obiekt współdzielony, z rozszerzeniem „.so”; na Windowsie: dynamiczną bibliotekę łączącą)

Istnieje jeszcze jedna rzecz, której musisz być świadomy. Na Windowsie musisz dokładnie sprecyzować, że procedura ma zostać wyeksportowana, np. widoczna w dynamicznej bibliotece. Jest kilka sposobów na osiągnięcie tego w zależności jakiego kompilatora używasz. Jedną z metod jest zrobienie tego poprzez tak zwaną dyrektywę kompilatora:

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

Lub, przez kompilator Intel Fortran:

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

Oprócz dynamicznej biblioteki (DLL), tak zwane biblioteki importowane mogą zostać wygenerowane.

Z uwagi na to, że detale różnią się w zależności od kompilatora, zamieszczone są tutaj dwa przykłady: gfortran na Cygwinie oraz Intel Fortran na Windowsie. W obu przypadkach pokazujemy program tabulate w pliku „tabulate.f90”.

GNU/Linux i gfortran#

Program tabulate wymaga zdefiniowanej przez użytkownika procedury f. Jeśli pozwolimy na przechowywanie jej w dynamicznej bibliotece, jak „functions.dll”, możemy łatwo zamienić implementację funkcji poprzez dodanie innej dynamicznej biblioteki do folderu. Nie ma potrzeby ponownej kompilacji programu.

Na systemie Cygwin nie jest konieczne jawne eksportowanie procedury - wszystkie publicznie widoczne rutyny są eksportowane podczas tworzenia dynamicznej biblioteki. Ponadto, nie jest generowana żadna biblioteki importowana.

Ponieważ nasza dynamiczna biblioteki może być stworzona z jednego pliku źródłowego, możemy zrobić to szybciej:

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

Dzięki temu zostały stworzone pliki „functions.dll” i „user_functions.mod”. Funkcja nm daje nam dokładną nazwę funkcji 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
...

Otrzymała ona prefiks __function_MOD_, aby rozróżnić ją od inny rutyn „f”, które mogą być zdefiniowane w innym module.

Następnym krokiem jest kompilacja programu:

$ gfortran -o tabulate tabulate.f90 functions.dll

DLL oraz plik .mod będą używane do skompilowania programu wykonywalnego ze sprawdzeniami interfejsu funkcji, poprawną nazwą oraz odniesieniem do „jakiegoś” DLL’a, nazwanego „functions.dll”.

Możesz zamienić bibliotekę współdzieloną „functions.dll” na inną, implementując inną funkcję „f”. Oczywiście, musisz być ostrożnym i zastosować poprawny interfejs dla tej funkcji. Kompilator/program łączący nie jest uruchamiany, dlatego nie może tego sprawdzić.

Windows i Intel Fortran#

Konfiguracja jest taka sama jak na Linuksie, jednak na Windowsie konieczne jest jawne eksportowanie rutyn. Tworzona jest również biblioteki importowana - jest to biblioteki, która powinna zostać użyta w procesie łączenia.

Plik źródłowy musi zawierać dyrektywę kompilatora, inaczej funkcja f nie zostanie wyeksportowana:

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

Ponownie możemy to zrobić szybciej:

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

Dzięki temu zostały stworzone pliki „functions.dll”, „user_functions.mod” oraz „functions.lib” (dwa pozostałe pliki nie są tutaj ważne). Program „dependency walker” daje nam dokładną nazwę funkcji „f”, czyli FUNCTION_mp_F. Jest ona również eksportowana, żeby mogła być znaleziona przez program łączący w kolejnym kroku:

$ ifort tabulate.f90 functions.lib

Zauważ, że musimy sprecyzować nazwę eksportowanej biblioteki, nie DLL’a!

(Zauważ również: kompilator Intel Fortran używa nazwy pierwszego pliku źródłowego jaki nazwy pliku wykonywalnego - tutaj bez opcji -exe.)

Tak samo jak na Cygwinie, DLL plik .mod są używane do skompilowanie programu wykonywalnego ze sprawdzeniem interfejsu funkcji, poprawną nazwą oraz odniesieniem do „jakiegoś” DLL’a, nazwanego „functions.dll”.

Możesz zastąpić bibliotekę współdzieloną „functions.dll” inną, ale musisz być ostrożnym: mimo tego, że implementacja może być inna, interfejs funkcji musi pozostać taki sam.