Создание приложения на Haskell с графическим интерфейсом .NET - PullRequest
7 голосов
/ 19 марта 2012

Я хотел бы создать приложение на Haskell с графическим интерфейсом .NET.Я хотел бы использовать cabal в качестве инструмента сборки, чтобы воспользоваться преимуществами управления пакетами и т. Д. Я думаю, что часть Haskell должна быть исполняемым файлом, который вызывает код .NET как:

  1. Это избавляет от необходимости вручную инициализировать RTC на Haskell, как описано здесь: http://www.haskell.org/ghc/docs/7.0.3/html/users_guide/win32-dlls.html

  2. Кабал не может легко создавать Windows-библиотеки: http://www.haskell.org/haskellwiki/Cabal/Developer-FAQ#Building_DLLs__with_Cabal

Я нашел это довольноЛегко создать исполняемый файл Haskell, который вызывает .NET, используя hs-dotnet , но мне также нужен мой код GUI для обратного вызова в Haskell.Я надеялся добиться этого с помощью команды «зарубежного экспорта» на Haskell, а затем вызывать эту экспортированную функцию с помощью встроенного взаимодействия .NET.Однако функция «внешнего экспорта», по-видимому, не создает точку входа в исполняемый файл, и я не вижу эту точку входа, когда выполняю dumpbin /EXPORTS для получившегося исполняемого файла.Я не уверен, так ли это, потому что GHC создает точки входа только при создании библиотеки DLL с помощью переключателя -shared, или же Cabal добавляет флаг, который подавляет создание точки входа.

Так что я думаю, вопрос в том, как сделатьЯ заставляю GHC создавать точки входа в мой исполняемый файл Windows?Или мне лучше использовать исполняемый файл .NET, выполнив необходимые шаги для создания DLL на Haskell с cabal и ручной инициализации RTC на Haskell?

1 Ответ

4 голосов
/ 19 марта 2012

Обычно я решаю эту проблему, передавая обратные вызовы в качестве указателей на функции. Например, у меня есть приложение, в котором нажатие кнопки требует обратного вызова в Haskell (я использую Какао, но, за исключением имен, он очень похож).

Сначала я создаю подкласс объекта NSButton и предоставляю моему новому классу ButtonC закрытый член типа void(*onClickCallback)(). Я также объявляю функцию void setClickCallback(ButtonC *button, void(*callback)()); Реализация вашего подкласса, чтобы при каждом нажатии кнопки вызывался указатель функции. В .Net может быть разумный способ сделать это с делегатами (давно я не использовал .Net).

Далее моя привязка на Haskell выглядит следующим образом (некоторый код опущен):

module Foreign.Button where

data ButtonObj
type ButtonPtr = ForeignPtr ButtonObj

foreign import ccall unsafe "setClickCallback"
  c_setClickCallback :: Ptr ButtonObj -> FunPtr (IO ()) -> IO ()

foreign import ccall "wrapper"
  makeClickCallback :: IO () -> IO (FunPtr (IO ()))

buttonSetCallback :: ButtonPtr -> FunPtr (IO ()) -> IO ()
buttonSetCallback btn cb =
    withForeignPtr btn $ \p -> c_setClickCallback p cb

buttonNew :: IO ButtonPtr
buttonNew = ...

Теперь данные на Haskell типа IO () можно обернуть в FunPtr и передать в GUI с помощью buttonSetCallback. При каждом нажатии кнопки выполняется действие IO ().

createButton :: IO ButtonPtr
createButton = do
    btn <- buttonNew
    let buttonClicked = print "The button was clicked!"
    btnCallback <- makeClickCallback buttonClicked
    buttonSetCallback btn btnCallback
    return btn

С FunPtr s следует опасаться, что они не являются сборщиком мусора. Вам нужно вручную освободить их, когда вы закончите, иначе у вас будет утечка памяти. Хорошей практикой является никогда не делиться FunPtr с, а также никогда не сохранять ссылки на них на стороне Хаскеля. Таким образом, ваш объект может освободить FunPtr как часть его очистки. Это требует еще одного обратного вызова в Haskell (freeFunPtr функция), который должен быть разделен между всеми вашими объектами и сам по себе освобождается только после завершения вашего исполняемого файла.

...