На некоторых платформах GHC поддерживает компиляцию кода Haskell в общие библиотеки. Общие библиотеки также иногда известны как динамические библиотеки, в частности, в Windows они называются динамическими библиотеками связи (DLL).
Общие библиотеки позволяют одному экземпляру предварительно скомпилированного кода быть общим для нескольких программ. В отличие от статической компоновки, код копируется в каждую программу. Использование общих библиотек позволяет экономить дисковое пространство. Они также позволяют использовать один экземпляр кода в памяти между несколькими программами, которые его используют. Общие библиотеки часто используются для структурирования больших проектов, особенно там, где разные части написаны на разных языках программирования. Общие библиотеки также часто используются в качестве механизма плагинов различными приложениями. Это особенно распространено в Windows с использованием COM.
В версии GHC 6.12 компиляция общих библиотек поддерживается для Linux (на архитектурах x86 и x86-64). Версия GHC 7.0 добавляет поддержку для Windows (см. Создание и использование библиотек DLL для Win32), FreeBSD и OpenBSD (x86 и x86-64), Solaris (x86) и Mac OS X (x86 и PowerPC).
Создание и использование общих библиотек немного сложнее, чем создание и использование статических библиотек. При использовании Cabal многие детали скрыты, просто используйте --enable-shared при настройке пакета для компиляции в общую библиотеку или для связи с другими пакетами, скомпилированными как общие библиотеки. Дополнительная сложность при компиляции кода заключается в различении того, будет ли код использоваться в общей библиотеке или будет использовать версии общей библиотеки других зависимых пакетов. Существует дополнительная сложность при установке и распространении общих библиотек или программ, использующих общие библиотеки, для обеспечения того, что все необходимые в процессе выполнения общие библиотеки присутствуют в соответствующих местах.
5.12.1. Создание программ, использующих общие библиотеки
Для создания простой программы, которая будет использовать общие библиотеки для системного времени выполнения и базовых библиотек, используйте флаг -dynamic:
ghc --make -dynamic Main.hs
Это имеет два эффекта. Первый — компиляция кода таким образом, чтобы его можно было связать с версиями общих библиотек пакетов Haskell (например, base). Второй — при компоновке связаться с общими версиями библиотек пакетов вместо статических. Очевидно, что для этого необходимо, чтобы пакеты были скомпилированы с общими библиотеками. На поддерживаемых платформах GHC поставляется с общими библиотеками для всех основных пакетов, но если вы установите дополнительные пакеты (например, с помощью Cabal), то их также нужно будет скомпилировать с общими библиотеками (--enable-shared для Cabal).
5.12.2. Общие библиотеки для пакетов Haskell
Вы можете скомпилировать код Haskell в общую библиотеку и создать пакет, который будет использоваться другими программами Haskell. Самый простой способ — использовать Cabal, просто настройте пакет Cabal с флагом --enable-shared.
Если вы хотите выполнить шаги вручную или пишете свою систему сборки, то необходимо соблюдать определённые соглашения. Создание общей библиотеки, экспортирующей код Haskell, для использования другим кодом Haskell, несколько сложнее, чем создание библиотеки, экспортирующей C-API и предназначенной для использования кодом C. Если вы сделаете это неправильно, обычно получите ошибки линковщика.
В частности, общие библиотеки Haskell должны быть упакованы в пакеты. Вы не можете свободно назначать модули в различные общие библиотеки. Общие библиотеки Haskell должны соответствовать границам пакетов. Причина в том, что GHC обрабатывает ссылки на символы внутри одной и той же общей библиотеки (или исполняемого файла) иначе, чем ссылки на символы между различными общими библиотеками. GHC должен знать для каждого импортированного модуля, находится ли этот модуль локально в той же общей библиотеке или в отдельной общей библиотеке. Для этого используется система пакетов. При использовании -dynamic, модуль из отдельного пакета предполагается из отдельной общей библиотеки, в то время как модули из одного пакета (или стандартного пакета «main») предполагаются в одной общей библиотеке (или в исполняемом файле).
Большинство соглашений, которые GHC ожидает при использовании пакетов, описаны в Создание пакета из исходного кода Haskell. Кроме того, обратите внимание, что GHC ожидает, что файлы .hi будут использовать расширение .dyn_hi. Другие требования такие же, как для C-библиотек, и описаны ниже, в частности, использование флагов -dynamic, -fPIC и -shared.
5.12.3. Общие библиотеки, экспортирующие C-API
Компиляция кода Haskell в общую библиотеку — хороший способ включить код Haskell в более крупный проект смешанного языка программирования. Хотя при статической компоновке рекомендуется использовать GHC для выполнения последнего шага компоновки, при использовании общих библиотек библиотека Haskell может рассматриваться как любая другая общая библиотека. Связывание можно выполнить с помощью стандартного компилятора или линковщика C.
Возможна загрузка общих библиотек, сгенерированных GHC, в другие программы, не написанные на Haskell, поэтому они подходят для использования в качестве плагинов. Конечно, для создания плагина вам потребуется использовать FFI для экспорта C-функций и соблюдать правила инициализации RTS. См. Создание библиотеки Haskell, которую можно вызывать из кода на другом языке. В частности, вы, вероятно, захотите экспортировать C-функцию из вашей общей библиотеки для инициализации плагина до вызова любых функций Haskell.
Для компиляции модулей Haskell, экспортирующих C-API в общую библиотеку, используйте флаги -dynamic, -fPIC и -shared:
ghc --make -dynamic -shared -fPIC -flink-rts Foo.hs -o libfoo.so
Как и прежде, флаг -dynamic указывает, что эта библиотека связывается с версиями общих библиотек пакета base. -flink-rts дополнительно связывает с версией общей библиотеки пакета rts (связь с пакетом rts по умолчанию не включается при построении общих библиотек). Вы также можете опустить -flink-rts и подключить библиотеку RTS к вашему исполняемому файлу.
Флаг -fPIC требуется для всего кода, который будет включен в общую библиотеку. Флаг -shared указывает на создание общей библиотеки, а не программы. Для большей ясности, мы можем разбить это на этапы компиляции и компоновки:
ghc -dynamic -fPIC -c Foo.hs ghc -dynamic -shared -flink-rts Foo.o -o libfoo.so
В принципе, вы можете использовать -shared без -dynamic на этапе компоновки. Это означает, что статически подключить систему времени выполнения и все базовые библиотеки к вашей новой общей библиотеке. Это приведет к очень большой, но автономной общей библиотеке. Однако на большинстве платформ для этого потребуется, чтобы все статические библиотеки были скомпилированы с -fPIC, чтобы код был подходящим для включения в общую библиотеку, и мы этого пока не делаем.
Предупреждение
Если ваша общая библиотека экспортирует Haskell-API, то вы не можете напрямую подключить ее к другой программе Haskell и использовать этот Haskell-API. Вы получите ошибки линковщика. Вместо этого необходимо упаковать ее в пакет, как описано в разделе выше.