Я пытаюсь написать приложение для Windows в Haskell.
Фон
Службу службы выполняет диспетчер управления службами Windows. После запуска он блокирует вызов StartServiceCtrlDispatcher, который поставляется с обратным вызовом, который будет использоваться в качестве основной функции .
Основная функция службы должна регистрировать второй обратный вызов для обработки входящих команд, таких как запуск, остановка, продолжение и т.д. Это делает это, вызывая RegisterServiceCtrlHandler.
Проблема
Я могу написать программу, которая будет регистрировать главную функцию службы. Затем я смогу установить программу в качестве службы Windows и запустить ее из консоли управления службами. Служба может запускаться, сообщать о себе как запуске, а затем ждать входящих запросов.
Проблема заключается в том, что я не могу получить функцию обработчика сервиса. Запрос состояния службы показывает, что он запущен, но как только я отправлю его, окна "stop" выведут сообщение с сообщением:
Windows could not stop the Test service on Local Computer.
Error 1061: The service cannot accept control messages at this time.
Согласно документации MSDN функция StartServiceCtrlDispatcher блокируется, пока все службы не сообщают, что они остановлены. После вызова главной функции службы поток диспетчера должен ждать, пока диспетчер управления сервисом отправит команду, после чего функция обработчика должна быть вызвана этим потоком.
Подробнее
Далее следует очень упрощенная версия того, что я пытаюсь сделать, но она демонстрирует проблему не вызываемой функции моего обработчика.
Во-первых, несколько имен и импорта:
module Main where
import Control.Applicative
import Foreign
import System.Win32
wIN32_OWN_PROCESS :: DWORD
wIN32_OWN_PROCESS = 0x00000010
sTART_PENDING, rUNNING :: DWORD
sTART_PENDING = 0x00000002
rUNNING = 0x00000004
aCCEPT_STOP, aCCEPT_NONE :: DWORD
aCCEPT_STOP = 0x00000001
aCCEPT_NONE = 0x00000000
nO_ERROR :: DWORD
nO_ERROR = 0x00000000
type HANDLER_FUNCTION = DWORD -> IO ()
type MAIN_FUNCTION = DWORD -> Ptr LPTSTR -> IO ()
Мне нужно определить несколько специальных типов данных со Storable экземплярами для сортировки данных:
data TABLE_ENTRY = TABLE_ENTRY LPTSTR (FunPtr MAIN_FUNCTION)
instance Storable TABLE_ENTRY where
sizeOf _ = 8
alignment _ = 4
peek ptr = TABLE_ENTRY <$> peek (castPtr ptr) <*> peek (castPtr ptr `plusPtr` 4)
poke ptr (TABLE_ENTRY name proc) = do
poke (castPtr ptr) name
poke (castPtr ptr `plusPtr` 4) proc
data STATUS = STATUS DWORD DWORD DWORD DWORD DWORD DWORD DWORD
instance Storable STATUS where
sizeOf _ = 28
alignment _ = 4
peek ptr = STATUS
<$> peek (castPtr ptr)
<*> peek (castPtr ptr `plusPtr` 4)
<*> peek (castPtr ptr `plusPtr` 8)
<*> peek (castPtr ptr `plusPtr` 12)
<*> peek (castPtr ptr `plusPtr` 16)
<*> peek (castPtr ptr `plusPtr` 20)
<*> peek (castPtr ptr `plusPtr` 24)
poke ptr (STATUS a b c d e f g) = do
poke (castPtr ptr) a
poke (castPtr ptr `plusPtr` 4) b
poke (castPtr ptr `plusPtr` 8) c
poke (castPtr ptr `plusPtr` 12) d
poke (castPtr ptr `plusPtr` 16) e
poke (castPtr ptr `plusPtr` 20) f
poke (castPtr ptr `plusPtr` 24) g
Необходимо произвести только три импортных импорта. Там импортируется "обертка" для двух обратных вызовов, которые я буду поставлять Win32:
foreign import stdcall "wrapper"
smfToFunPtr :: MAIN_FUNCTION -> IO (FunPtr MAIN_FUNCTION)
foreign import stdcall "wrapper"
handlerToFunPtr :: HANDLER_FUNCTION -> IO (FunPtr HANDLER_FUNCTION)
foreign import stdcall "windows.h RegisterServiceCtrlHandlerW"
c_RegisterServiceCtrlHandler
:: LPCTSTR -> FunPtr HANDLER_FUNCTION -> IO HANDLE
foreign import stdcall "windows.h SetServiceStatus"
c_SetServiceStatus :: HANDLE -> Ptr STATUS -> IO BOOL
foreign import stdcall "windows.h StartServiceCtrlDispatcherW"
c_StartServiceCtrlDispatcher :: Ptr TABLE_ENTRY -> IO BOOL
Основная программа
Наконец, вот основное служебное приложение:
main :: IO ()
main =
withTString "Test" $ \name ->
smfToFunPtr svcMain >>= \fpMain ->
withArray [TABLE_ENTRY name fpMain, TABLE_ENTRY nullPtr nullFunPtr] $ \ste ->
c_StartServiceCtrlDispatcher ste >> return ()
svcMain :: MAIN_FUNCTION
svcMain argc argv = do
appendFile "c:\\log.txt" "svcMain: svcMain here!\n"
args <- peekArray (fromIntegral argc) argv
fpHandler <- handlerToFunPtr svcHandler
h <- c_RegisterServiceCtrlHandler (head args) fpHandler
_ <- setServiceStatus h running
appendFile "c:\\log.txt" "svcMain: exiting\n"
svcHandler :: DWORD -> IO ()
svcHandler _ = appendFile "c:\\log.txt" "svcCtrlHandler: received.\n"
setServiceStatus :: HANDLE -> STATUS -> IO BOOL
setServiceStatus h status = with status $ c_SetServiceStatus h
running :: STATUS
running = STATUS wIN32_OWN_PROCESS rUNNING aCCEPT_STOP nO_ERROR 0 0 3000
Выход
Ранее я установил службу с помощью sc create Test binPath= c:\Main.exe
.
Вот результат компиляции программы:
C:\path>ghc -threaded --make Main.hs
[1 of 1] Compiling Main ( Main.hs, Main.o )
Linking Main.exe ...
C:\path>
Затем я запускаю службу из монитора управления сервисом. Вот доказательство того, что мой вызов SetServiceStatus был принят:
C:\Path>sc query Test
SERVICE_NAME: Test
TYPE : 10 WIN32_OWN_PROCESS
STATE : 4 RUNNING
(STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
WIN32_EXIT_CODE : 0 (0x0)
SERVICE_EXIT_CODE : 0 (0x0)
CHECKPOINT : 0x0
WAIT_HINT : 0x0
C:\Path>
Вот содержимое log.txt
, доказывающее, что мой первый обратный вызов svcMain
был вызван:
svcMain: svcMain here!
svcMain: exiting
Как только я отправлю команду остановки с помощью диспетчера управления службами, я получаю сообщение об ошибке. Моя функция обработчика должна была добавить строку в файл журнала, но этого не происходит. Затем мое обслуживание появляется в остановленном состоянии:
C:\Path>sc query Test
SERVICE_NAME: Test
TYPE : 10 WIN32_OWN_PROCESS
STATE : 1 STOPPED
WIN32_EXIT_CODE : 0 (0x0)
SERVICE_EXIT_CODE : 0 (0x0)
CHECKPOINT : 0x0
WAIT_HINT : 0x0
C:\Path>
Вопрос
Есть ли у кого-нибудь идеи о том, что я могу попытаться вызвать функцию моего обработчика?
Обновление 20130306
У меня эта проблема на 64-разрядной версии Windows 7, но не на Windows XP. Другие версии Windows еще не были протестированы. Когда я копирую скомпилированный исполняемый файл на несколько машин и выполняю те же шаги, я получаю разные результаты.