Spec-Zone.ru › Haskell 9

13. Запуск GHC в системах Win32

13.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.

13.2. Запуск GHCi в Windows

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

При запуске GHCi в оболочке Cygwin или MSYS обработка Control-C работает некорректно. В таких средах для запуска GHCi следует использовать скрипт ghcii.sh; в противном случае при нажатии 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.

13.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!

13.4. Различия в поведении библиотек

Некоторые стандартные библиотеки Haskell в Windows работают немного иначе.

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

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

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

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

Существуют следующие пространства имён:

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

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

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

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

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

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

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

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

13.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).

13.6.2. Проблема

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

13.6.3. Что делать

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

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

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

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

Есть два разных способа использования DLL:

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

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

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

13.7.1. Создание DLL

Создание DLL Win32 -shared Упаковать библиотеку Haskell в DLL довольно просто: скомпилируйте объектные файлы, из которых состоит библиотека, а затем создайте DLL командой следующего вида:

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

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

Обратите внимание на несколько особенностей:

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

    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 в командную строку.

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

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

Используйте объявления foreign export, чтобы экспортировать функции 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 ccall adder :: Int -> Int -> IO Int

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

// 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).

13.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 — после последней, всё будет работать правильно.

13.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/9.14.1/docs/users_guide/win32-dlls.html

Spec-Zone.ru

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