Spec-Zone.ru › Haskell 8

16. Запуск GHC на системах Win32

16.1. Запуск GHC на платформах Windows

Инсталлятор, устанавливающий GHC на Win32, также настраивает ассоциации расширений файлов для файлов с расширениями «.hs» и «.lhs», чтобы двойной щелчок по ним запускал ghci.

Обратите внимание, что ghc и ghci требуют, чтобы имена файлов, содержащие пробелы, были экранированы с помощью кавычек:

c:\ghc\bin\ghci "c:\\Program Files\\Haskell\\Project.hs"

Если кавычки в указанной выше команде опущены, ghci будет интерпретировать имя файла как два, c:\\\\Program и Files\\\\Haskell\\\\Project.hs.

16.2. Запуск GHCi на Windows

Рекомендуется запускать GHCi в стандартной консоли Windows: выберите опцию GHCi из пункта меню «Пуск», добавленного установщиком GHC, или используйте Start->Run->cmd для получения консоли Windows и вызова ghci оттуда (если он находится в вашем PATH).

Если вы запускаете GHCi в оболочке Cygwin или MSYS, поведение клавиши Control-C ухудшается. В одной из этих сред вы должны использовать скрипт ghcii.sh для запуска GHCi, иначе при нажатии Control-C вы вернетесь к приглашению оболочки, но процесс GHCi по-прежнему будет выполняться. Однако, даже используя скрипт ghcii.sh , если вы нажмете Control-C, процесс GHCi будет убит немедленно, а не позволит прервать запущенную программу внутри GHCi, как это должно быть. Эта проблема вызвана тем, что среды оболочек Cygwin и MSYS не передают события Control-C дочерним процессам, не являющимся Cygwin, поскольку для этого требуется консоль Windows.

Есть исключение: вы можете использовать оболочку Cygwin, если переменная окружения CYGWIN не содержит tty. В этом режиме оболочка Cygwin ведет себя как оболочка консоли Windows, и события консоли передаются дочерним процессам. Обратите внимание, что переменная среды CYGWIN должна быть установлена перед запуском оболочки Cygwin; изменение ее после этого не повлияет на оболочку.

Эта проблема затрагивает не только GHCi, но и любые программы, скомпилированные GHC, которые хотят перехватывать события консоли. См. модуль GHC.ConsoleHandler.

16.3. Взаимодействие с терминалом

По умолчанию GHC строит приложения, которые открывают окно консоли при запуске. Если вы хотите создать приложение только с графическим интерфейсом, без окна консоли, используйте флаг -optl-mwindows на этапе связывания.

Предупреждение

Программы Windows только с графическим интерфейсом не имеют stdin, stdout или stderr, поэтому использование обычных функций ввода/вывода Haskell приведет к сбою вашей программы с исключением IO, например:

Fail: <stdout>: hPutChar: failed (Bad file descriptor)

Однако использование Debug.Trace.trace приемлемо, так как оно использует поддержку отладки вывода Windows, а не stderr.

По какой-то причине Mingw поставляется с библиотекой readline, но не с заголовками readline. В результате GHC (как и Hugs) не использует readline для интерактивного ввода на Windows. Вы можете получить близкую имитацию, используя буфер оболочки emacs!

16.4. Отличия в поведении библиотек

Некоторые стандартные библиотеки Haskell ведут себя немного по-разному на Windows.

  • В Windows символ ^Z интерпретируется как символ конца файла, поэтому при чтении файла, содержащего этот символ, файл будет казаться заканчивающимся непосредственно перед ним. Чтобы избежать этого, используйте IOExts.openFileEx для открытия файла в двоичном (непереведенном) режиме или измените уже открытый дескриптор файла на двоичный режим, используя IOExts.hSetBinaryMode. Модуль IOExts является частью пакета lang.

16.5. Пути к файлам в Windows

Пути к файлам в Windows не одинаковы. Различные типы путей имеют разное значение. Ограничение MAX_PATH не является ограничением операционной системы или файловой системы. Это ограничение по умолчанию, навязанное API Win32 для обеспечения обратной совместимости.

Однако ядро NT предоставляет способы отказаться от этой обработки путей API Win32. Это делается путем явного использования требуемого пространства имен в пути.

Пространства имен:

  • Пространство имен файла: \\?\
  • Пространство имен устройства: \\.\
  • Пространство имен NT: \

Каждый из них полностью отключает обработку путей API Win32, и пути передаются неизменными файловой системе.

Пути с буквой диска являются устаревшими путями. Буквы диска на самом деле не имеют значения для ядра. Подобно операционным системам Unix, буквы дисков — это просто точки монтирования. Вы можете просмотреть свои точки монтирования, используя команду mountvol.

Начиная с GHC 8.6.1, менеджер ввода/вывода Haskell автоматически преобразует пути в устаревшем формате в пространство имен файлов Win32. По умолчанию менеджер ввода/вывода выполнит два действия с вашими путями:

  • заменить \ на \\
  • преобразовать относительные пути в абсолютные пути

Если вы хотите отказаться от всей предварительной обработки, просто явно используйте пространства имен в своих путях. Из-за этого изменения, если вам необходимо открыть необработанные устройства (например, порты COM), вам нужно явно использовать пространство имен устройств. (например, \\.\COM1). GHC и программы Haskell в целом больше не поддерживают открытие устройств в устаревшем формате.

Дополнительные сведения см. в документации Windows.

16.6. Использование GHC (и других исполняемых файлов, скомпилированных GHC) с Cygwin

16.6.1. Предыстория

Инструменты Cygwin стремятся предоставить API в стиле Unix поверх библиотек Windows для облегчения портирования программного обеспечения Unix на Windows. Для этого они вводят иерархию каталогов Unix под некоторым корневым каталогом (обычно / — C:\cygwin\). Кроме того, все, что построено на API Cygwin (включая инструменты Cygwin и программы, скомпилированные с помощью GHC Cygwin), будет видеть / в качестве корня своей файловой системы, имитируя работу в типичной среде Unix и находя такие вещи, как /bin и /usr/include , никогда не беспокоясь об их фактическом расположении в системе Windows (вероятно, C:\cygwin\bin и C:\cygwin\usr\include).

16.6.2. Проблема

GHC по умолчанию больше не зависит от Cygwin, а является собственной программой Windows. Он создан с использованием mingw и использует mingw’s GHC для компиляции ваших Haskell-исходников (даже если вы вызываете его из bash Cygwin), но здесь важно то, что — точно так же, как и любая другая обычная программа Windows — ни GHC, ни созданные им исполняемые файлы не осведомлены об имитируемой Unix-иерархии Cygwin. GHC с удовольствием примет / или \\ в качестве разделителей путей, но не будет знать, где найти /home/joe/Main.hs или /bin/bash или аналогичные. Это вызывает всевозможные проблемы, когда GHC используется из bash Cygwin или в сессиях make, выполняемых под Cygwin.

16.6.3. Что делать

  • Не используйте абсолютные пути в make, configure и т. д., если есть вероятность, что они будут переданы GHC (или программам, скомпилированным GHC). Относительные пути подходят, потому что инструменты Cygwin работают с ними, а GHC принимает / в качестве разделителя путей. Относительные пути не зависят от расположения корневого каталога Cygwin или от того, на какой раздел или сетевой диск находится ваша структура исходных файлов, если вы их туда cd.
  • Если вам нужно использовать абсолютные пути (остерегайтесь незаметного ROOT=$(pwd) в иерархиях makefile или скриптах конфигурации), Cygwin предоставляет инструмент cygpath, который может преобразовать пути Cygwin в стиле Unix в их фактические пути Windows. Многие инструменты Cygwin на самом деле принимают абсолютные пути в стиле Windows (пожалуйста, помните, что вам нужно либо экранировать \\ , или преобразовать \\ в / ), поэтому вы можете использовать их везде. Если вам нужны инструменты, которые выполняют какую-то манипуляцию с путями, зависящую от путей в стиле Unix (один забавный пример — попытка интерпретировать : как разделитель в списках путей), вы все еще можете попробовать преобразовать пути с помощью cygpath непосредственно перед тем, как передать их GHC и другим программам.
  • Если у вас нет cygpath, у вас, вероятно, нет Cygwin, и поэтому нет проблем с ним… за исключением случаев, когда вы хотите написать один процесс сборки для нескольких платформ. Опять же, относительные пути — ваши друзья, но если вам нужно использовать абсолютные пути и не хотите использовать разные инструменты на разных платформах, вы можете просто написать небольшую программу Haskell, чтобы вывести текущий каталог (спасибо George Russell за эту идею): скомпилированная с помощью GHC, она даст вам представление о файловой системе, от которой зависит GHC (которое будет отличаться в зависимости от того, скомпилирован ли GHC с помощью gcc Cygwin или mingw или на реальной системе Unix …) — эта небольшая программа также может обрабатывать экранирование \\ в путях. Кроме баннера и времени запуска, что-то вроде этого тоже будет работать:

    $ echo "Directory.getCurrentDirectory >>= putStrLn . init . tail . show " | ghci
    

16.7. Создание и использование DLL Win32

Динамические библиотеки, DLL Win32, Win32 На платформах Win32 компилятор может создавать и использовать динамические библиотеки DLL, содержащие код, скомпилированный GHC. Этот раздел показывает, как использовать эту возможность.

Существует два различных способа использования DLL:

  • Вы можете преобразовать каждый пакет Haskell в DLL, чтобы несколько исполняемых файлов Haskell, использующих одни и те же пакеты, могли совместно использовать файлы DLL. (В отличие от статического связывания библиотек, которое фактически создает новую копию RTS и всех библиотек для каждого созданного исполняемого файла.)

    Это то же самое, что и динамическое связывание на других платформах, и оно описано в Использование общих библиотек.

  • Вы можете упаковать всю программу Haskell в DLL для вызова внешней (как правило, не Haskell) программой. Это обычно используется для реализации плагинов и т. п., и описано ниже.

16.7.1. Создание DLL

Создание Win32 DLL — совместная запечатка вашей библиотеки Haskell внутри DLL проста; скомпилируйте объектные файлы, составляющие библиотеку, а затем создайте DLL, выполнив команду следующего вида:

ghc -shared -o foo.dll bar.o baz.o wibble.a -lfooble

Передав драйверу компилятора ghc опцию -shared, он создаст DLL вместо исполняемого файла. DLL будет состоять из всех объектных файлов и архивов, указанных в командной строке.

Несколько моментов для замечания:

  • По умолчанию, точки входа всех объектных файлов будут экспортированы из DLL при использовании -shared. Если вы хотите ограничить это, вы можете указать файл определения модуля для использования в командной строке следующим образом:

    ghc -shared -o .... MyDef.def
    

    См. документацию Microsoft для получения подробностей, но файл определения модуля просто перечисляет точки входа, которые вы хотите экспортировать. Вот один, подходящий для создания DLL-сервера Haskell COM:

    EXPORTS
     DllCanUnloadNow     = DllCanUnloadNow@0
     DllGetClassObject   = DllGetClassObject@12
     DllRegisterServer   = DllRegisterServer@0
     DllUnregisterServer = DllUnregisterServer@0
    
  • В дополнение к созданию DLL, опция -shared также создает библиотеку импорта. Имя библиотеки импорта получено из имени DLL следующим образом:

    DLL: HScool.dll  ==> import lib: libHScool.dll.a
    

    Схема именования может показаться немного странной, но она предназначена для совместного существования библиотек импорта с обычными статическими библиотеками (например, libHSfoo.a и libHSfoo.dll.a. Кроме того, когда драйвер компилятора связывает нестатическим способом, он перепишет вхождение -lHSfoo в командной строке на -lHSfoo.dll. Таким образом, переход от нестатической к статической компоновке — это просто вопрос добавления -static в командную строку.

16.7.2. Создание DLL для вызова из других языков

Этот раздел описывает, как создавать DLL для вызова из других языков, таких как Visual Basic или C++. Это особый случай Создание библиотеки Haskell, которая может вызываться из внешнего кода; ниже мы рассмотрим возникающие проблемы, специфичные для DLL. Вот пример:

Используйте объявления внешнего экспорта для экспорта функций Haskell, которые вы хотите вызвать извне. Например:

-- Adder.hs
{-# LANGUAGE ForeignFunctionInterface #-}
module Adder where

adder :: Int -> Int -> IO Int  -- gratuitous use of IO
adder x y = return (x+y)

foreign export stdcall adder :: Int -> Int -> IO Int

Добавьте вспомогательный код, который запускает и завершает Haskell RTS:

// StartEnd.c
#include <Rts.h>

void HsStart()
{
   int argc = 1;
   char* argv[] = {"ghcDll", NULL}; // argv must end with NULL

   // Initialize Haskell runtime
   char** args = argv;
   hs_init(&argc, &args);
}

void HsEnd()
{
   hs_exit();
}

Здесь, Adder — имя корневого модуля в дереве модулей (как упоминалось выше, должен быть один корневой модуль и, следовательно, одно дерево модулей в DLL). Скомпилируйте всё:

ghc -c Adder.hs
ghc -c StartEnd.c
ghc -shared -o Adder.dll Adder.o Adder_stub.o StartEnd.o

Теперь файл Adder.dll можно использовать из других языков программирования. Перед вызовом любых функций в Adder необходимо вызвать HsStart, а в самом конце вызвать HsEnd.

Предупреждение

Может показаться заманчивым использовать DllMain для вызова hs_init/hs_exit, но это не сработает (особенно если вы компилируете с -threaded). Существуют строгие ограничения на действия, которые могут быть выполнены во время DllMain, и hs_init нарушает эти ограничения, что может привести к зависанию вашей DLL при запуске (см. #3605).

16.7.2.1. Использование из VBA

Пример использования Adder.dll из VBA:

Private Declare Function Adder Lib "Adder.dll" Alias "adder@8" _
      (ByVal x As Long, ByVal y As Long) As Long

Private Declare Sub HsStart Lib "Adder.dll" ()
Private Declare Sub HsEnd Lib "Adder.dll" ()

Private Sub Document_Close()
HsEnd
End Sub

Private Sub Document_Open()
HsStart
End Sub

Public Sub Test()
MsgBox "12 + 5 = " & Adder(12, 5)
End Sub

В этом примере используются функции Document_Open/Close Microsoft Word, но при условии, что HsStart вызывается перед первой функцией, а HsEnd — после последней, тогда всё будет работать нормально.

16.7.2.2. Использование из C++

Пример использования Adder.dll из C++:

// Tester.cpp
#include "HsFFI.h"
#include "Adder_stub.h"
#include <stdio.h>

extern "C" {
    void HsStart();
    void HsEnd();
}

int main()
{
    HsStart();
    // can now safely call functions from the DLL
    printf("12 + 5 = %i\n", adder(12,5))    ;
    HsEnd();
    return 0;
}

Это можно скомпилировать и запустить с помощью:

$ ghc -o tester Tester.cpp Adder.dll.a
$ tester
12 + 5 = 17

© 2002–2007 The University Court of the University of Glasgow. All rights reserved.
Licensed under the Glasgow Haskell Compiler License.
https://downloads.haskell.org/~ghc/8.10.2/docs/html/users_guide/win32-dlls.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API