如何編譯並運作 Win32-services 支持的 Windows Service ?

上一篇「如何編譯 Hackage Win32-service ?」我摸清楚了 Cabal 與 Hackages 的版本關係。而在這篇,記錄運用 hackage Win32-services 製作一個 Windows Services ,並放在系統上實地運作。

總之簡要:

一、選配相關 hackages ,決定一件 Windows Service 的內容,並撰寫為程式。

二、(本篇重點)使用 Cabal 將上述程式編譯成可加掛到 Windows Services 的目標檔案。

本篇主要參考資訊為 Win32-services Cabal 專案說明檔,包括其中的範例程式與編譯方式。

選配相關 Hackages

要搭建一件 Windows Service ,須考慮二樣東西:一是 hackage Win32-services ,著力在銜接 Windows Services 的通訊控制架構,二是 hackage process ,幫忙將外部程式啟動為作業行程。

目前已知 hackage Win32-services 受 GHC 8.6.5 支援。而若用更晚近版本的 GHC ,則有點障礙,不好編譯。

所以,先用指令 ghcup set ghc 8.6.5 ,切換以 GHC 8.6.5 支援 Cabal 編譯

用指令 cabal init 開一件新的 Cabal 專案目錄,然後,將專案 .cabal 檔案內加上一段配置段落:

executable emacs-service-in-windows
  main-is:          Main.hs


  build-depends:    base   >= 4.5 && < 5.0
                  , Win32  >= 2.2 && < 2.9
                  , Win32-errors >= 0.2 && < 0.3
                  , Win32-services
                  , process

  ghc-options:     -Wall
  hs-source-dirs:   app
  default-language: Haskell2010

專案名稱為 emacs-service-in-windows ,未來會編譯出 emacs-service-in-windows.exe 檔案。

因為是 Windows Services 須用,所以,該配置段落為 executable 而非 library 。

主程式目錄在 app\ ,主程式檔名為 Main.hs 。

所用到的依存軟體,主要是 hackages Win32-process 與 process 。而其他項目為編譯時 Cabal 會配算並要求提供的版本資訊,由於我已確認清楚上述二項 hackages 所依賴,大致符合於 Win32-services 的依存軟體,所以直接翻抄其設定值。

然後,在 app\Main.hs 檔案內,借用 Win32-services 的範例檔案來繼續說明。所以,內容寫:

module Main where

import Control.Concurrent.MVar
import System.Win32.Services
import qualified System.Win32.Error as E

main = do
    mStop <- newEmptyMVar
    startServiceCtrlDispatcher "Test" 3000 (handler mStop) $ \_ _ h -> do
        setServiceStatus h running
        takeMVar mStop
        setServiceStatus h stopped

handler mStop hStatus Stop = do
    setServiceStatus hStatus stopPending
    putMVar mStop ()
    return True
handler _ _ Interrogate = return True
handler _ _ _           = return False

running = ServiceStatus Win32OwnProcess Running [AcceptStop] E.Success 0 0 0
stopped = ServiceStatus Win32OwnProcess Stopped [] E.Success 0 0 0
stopPending = ServiceStatus Win32OwnProcess StopPending [AcceptStop] E.Success 0 0 0

主程式函數 main 的最後有一段這樣的程式,為服務內容,

\_ _ h -> do
        setServiceStatus h running
        takeMVar mStop
        setServiceStatus h stopped

我想要啟動外部程式如 emacs --daemon ,指令將藉由 hackage process 裡的函數 callProcess 啟動為作業行程,就該將那段程式塞在以上這段服務內容內。(我猜要塞在 setServiceStatus h running 句之前。)

用 Cabal 編譯為 Windows Service 目標檔案

在 Win32-services 專案說明檔裡提到使用 GHC 編譯的指令為

C:programmingtest>ghc --make -threaded Main.hs
[1 of 1] Compiling Main             ( Main.hs, Main.o )
Linking Main.exe ...
<linker warnings omitted>
C:\programming\test>copy Main.exe c:\svc\Test.exe
1 file(s) copied.

換為 Cabal 編譯指令,則是

> cabal build --ghc-options=-threaded

要有 GHC 導引指令 --threaded ,之後目標檔案掛載為 Windows Service 之後,才可順暢地啟動服務或關閉服務

如我的例子,編譯訊息為

PS C:\work\emacs-service-in-windows> cabal build --ghc-options=-threaded
Build profile: -w ghc-8.6.5 -O1
In order, the following will be built (use -v for more details):
 - emacs-service-in-windows-0.1.0.0 (exe:emacs-service-in-windows) (configuration changed)
Configuring executable 'emacs-service-in-windows' for emacs-service-in-windows-0.1.0.0..
Preprocessing executable 'emacs-service-in-windows' for emacs-service-in-windows-0.1.0.0..
Building executable 'emacs-service-in-windows' for emacs-service-in-windows-0.1.0.0..
Linking C:\work\emacs-service-in-windows\dist-newstyle\build\x86_64-windows\ghc-8.6.5\emacs-service-in-windows-0.1.0.0\x\emacs-service-in-windows\build\emacs-service-in-windows\emacs-service-in-windows.exe ...

編譯後的 Linking 訊息指出目標檔案路徑

接著,切換到目標檔案目錄。

cd dist-newstyle\build\x86_64-windows\ghc-8.6.5\emacs-service-in-windows-0.1.0.0\x\emacs-service-in-windows\build\emacs-service-in-windows\

然後用 sc 指令( Service Control )將目標檔案放到 Windows Services :

sc create "Test 2" binPath= C:\\work\\emacs-service-in-windows\\dist-newstyle\\build\\x86_64-windows\\ghc-8.6.5\\emacs-service-in-windows-0.1.0.0\\x\\emacs-service-in-windows\\build\\emacs-service-in-windows\\emacs-service-in-windows.exe

這樣,就能用指令 sc start 啟動服務,或開啟 Windows Services 管理視窗去啟動服務。

也可以用 Powershell 的指令 New-ServiceStart-Service 啟動指令。

記得,提供給指令 sc createNew-Service 的目標檔案名稱,要包含全路徑

About yauhsien

A Prolog & Erlang lover.
本篇發表於 emacs, Haskell。將永久鏈結加入書籤。

發表留言