Gerenciando bibliotecas (bibliotecas estáticas e dinâmicas)#

Se você precisa gerenciar um programa com dúzias de arquivos fonte (e isso não é incomum!), o comando em linha necessário para especificar todos os arquivos objeto será bem grande. Isso logo fica tedioso ou até mesmo impossível de manter. Então uma solução diferente é necessária: criar suas próprias bibliotecas.

Bibliotecas contêm qualquer quantidade de arquivos objeto de uma forma compactada, para que os comandos em linha se tornem bem menores:

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

onde «supportlib.a» é uma coleção de um, dois ou mais arquivos objetos, todos compilados e adicionados em uma biblioteca. A extensão «.a» é usa no Linux e plataformas semelhantes. No Windows é usada a extensão «.lib».

Criar suas próprias bibliotecas não é complicado: no Linux, você consegue usando um utilitário como 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 ...

ou no Windows usando o utilitário 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 ...

Nota:

  • O comando ar com a opção r ou cria a biblioteca (o nome aparece depois da opção) ou adiciona novos arquivos objeto para a biblioteca (ou substitui qualquer existente).

  • O comando lib criará uma nova biblioteca se você específica a opção /out: com o nome de uma nova biblioteca próxima a ela. Para adicionar arquivos objetos para uma biblioteca existente, não use /out:.

  • Nas plataformas semelhantes ao Linux há a convenção particular de nomear bibliotecas. Se você nomear sua biblioteca como «libname.a» (note o prefixo «lib»), então você pode se referir a ela como -lname na fase de link.

  • Bibliotecas são frequentemente buscadas em diretórios indicados por uma opção -L ou /LIBPATH. Isso te poupa de ter que especificar o caminho exato para cada biblioteca.

Usando bibliotecas você pode construir programas gigantes sem ter que depender de linhas de comando extremamente longas.

bibliotecas estáticas versus dinâmicas#

A discussão acima assume tacitamente que você está usando as chamadas bibliotecas estáticas. Bibliotecas estáticas (ou pelo menos parte dela) se torna uma parte integral do programa executável. A única forma de mudar as rotinas incorporadas no programa é realizando o rebuild do programa com uma nova versão da biblioteca.

Uma alternativa flexível é usar as chamadas bibliotecas dinâmicas. Essas bibliotecas permanecem fora do código executável e como consequência podem ser substituídas sem refazer o build do programa completamente. Compiladores e o próprio sistema operacional são altamente dependentes de bibliotecas dinâmicas. Você pode considerar bibliotecas dinâmicas como programas executáveis que precisam de um pouco de ajuda para rodar.

Construir bibliotecas dinâmicas funciona de forma ligeiramente diferente de construir bibliotecas estáticas: você usa o compilador/linker ao invés de uma ferramenta como ar ou lib.

No Linux:

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

No Windows, com o compilador Intel Fortran:

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

As diferenças são que:

  • Você precisa especificar a opção de compilação no Linux, no gfortran é -fpic, porque o código objeto é ligeiramente diferente.

  • Você precisa informar na fase de link que você quer uma biblioteca dinâmica (no Linux: um objeto/biblioteca compartilhado, por isso a extensão «.so»; no Windows: um link de biblioteca dinâmico)

Há mais de uma coisa para se manter atento: No Windows você precisa especificar explicitamente que um procedimento é exported, p.ex. é visível na biblioteca dinâmica. Há várias formas - dependendo do compilador que você usa - para conseguir isso. Um método é através da chamada diretiva do compilador:

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

Ou, com o compilador Intel Fortran:

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

Além da biblioteca dinâmica (DLL), a chamada biblioteca de importação pode ser gerada.

Por conta dos detalhes serem diferentes em cada compilador, aqui dois exemplos: gfortran no Cygwin e Intel Fortran no Windows. Em ambos os casos, olharemos o programa tabulate no arquivo «tabulate.f90».

GNU/Linux e gfortran#

O programa tabulate requer uma rotina definida pelo usuário f. Se deixarmos numa biblioteca dinâmica, digamos «functions.dll», nós podemos simplesmente substituir a implementação da função colocando outra biblioteca dinâmica no diretório. Não precisa refazer o build do programa.

No Cygwin não é necessário exportar explicitamente um procedimento - todas as rotinas publicamente visíveis são exportadas quando você constrói uma biblioteca dinâmica. Além disso, nenhuma biblioteca de importação é gerada.

Já que nossa biblioteca dinâmica pode ser construída de um único arquivo fonte, nós podemos tomar um atalho:

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

Isso produz os arquivos «functions.dll» e «user_functions.mod». O utilitário nm nos diz o nome exato da função 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
...

Ele recebeu um prefixo __function_MOD_ para distingui-lo de qualquer outra rotina «f» que possa ser definida em outro módulo.

O próximo passo é realizar o build do programa:

$ gfortran -o tabulate tabulate.f90 functions.dll

O DLL é o arquivo .mod são usadas para realizar o build do programa executável com checagem na interface da função, o nome correto e a referência para «uma» DLL, chamada «functions.dll».

Você pode substituir a biblioteca compartilhada «functions.dll» por outra, implementando uma função diferente «f». Claro, você precisa ter cuidado ao usar a interface correta para essa função. O compilador/linker não é mais invocado, então não podem fazer checagem.

Windows e Intel Fortran#

O setup é o mesmo do Linux, mas no Windows é necessário exportar explicitamente as rotinas. E uma biblioteca de importação é gerada - essa é a biblioteca que deve ser usada na fase de link.

O arquivo fonte deve conter a diretiva do compilador, ou a função f não é exportada:

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

De novo, tomamos um atalho:

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

Isso produz os arquivos «functions.dll», «user_functions.mod» assim como «functions.lib» (e outros dois arquivos sem importância para cá). O programa «andador de dependências» nos diz que o nome exato da função «f» é FUNCTION_mp_F. Isso também é exportado para que também possa ser encontrado pelo linker na próxima fase:

$ ifort tabulate.f90 functions.lib

Note que precisamos especificar o nome da biblioteca de exportação, não a DLL!

(Note também: o compilador Intel Fortran usa o nome do primeiro arquivo fonte como o nome do executável - aqui fazemos sem a opção -exe.)

No Cygwin, a DLL e o arquivo .mod são usados para o build do programa executável com checagem na interface da função, no nome correto e na referência para «uma» DLL, chamada «functions.dll».

Você pode substituir a biblioteca compartilhada «functions.dll» por outra, mas a mesma atenção é necessária: enquanto que a implementação pode ser diferente, a interface da função deve ser a mesma.