I admit, this problem has been vexing me for some days now. From walking the return values and the contents of GetLastError, I’ve determined that this code should be working correctly according to the system.
Because it clearly isn’t (it seems to enter an undefined state that inhibits the service handler from running successfully), I’ve posted my full diagnosis and a workaround. This is the exact kind of scenario Microsoft should be made aware of, because its interface guarantees aren’t being honored.
Inspection
After becoming greatly unsatisfied with the error messages being reported by Windows when I attempted to interrogate the service (via sc interrogate service and sc control service with a canned control option allowed), I wrote my own call into GetLastError to see if anything interesting was going on:
import Text.Printf
import System.Win32
foreign import stdcall "windows.h GetLastError"
c_GetLastError :: IO DWORD
...
d <- c_GetLastError
appendFile "c:\\log.txt" (Text.Printf.printf "%d\n" (fromEnum d))
What I discovered, much to my chagrin, was that ERROR_INVALID_HANDLE and ERROR_ALREADY_EXISTS were being thrown… when you run your appendFile operations sequentially. Phooey, and here I’d thought I was on to something.
What this did tell me, however, is that StartServiceCtrlDispatcher, RegisterServiceCtrlHandler, and SetServiceStatus aren’t setting an error code; indeed, I get ERROR_SUCCESS exactly as hoped.
Analysis
Encouragingly, Windows’ Task Manager and System Logs register the service as RUNNING. So, assuming that piece of the equation is actually working, we must return to why our service handler isn’t being hit properly.
Inspecting these lines:
fpHandler <- handlerToFunPtr svcHandler
h <- c_RegisterServiceCtrlHandler (head args) fpHandler
_ <- setServiceStatus h running
I attempted to inject nullFunPtr in as my fpHandler. Encouragingly, this caused the service to hang in the START_PENDING state. Good: that means the contents of fpHandler are actually being handled when we register the service.
Then, I tried this:
t <- newTString "Foo"
h <- c_RegisterServiceCtrlHandler t fpHandler
And this, unfortunately, took. However, that’s expected:
If the service is installed with the
SERVICE_WIN32_OWN_PROCESSservice
type, this member is ignored, but cannot be NULL. This member can be
an empty string (“”).
According to our hooked GetLastError and the returns from RegisterServiceCtrlHandler and SetServiceStatus (a valid SERVICE_STATUS_HANDLE and true, respectively), all is well according to the system. That can’t be right, and it’s completely opaque as to why this doesn’t just work.
Current Workaround
Because it’s unclear if your declaration into RegisterServiceCtrlHandler is working effectively, I recommend interrogating this branch of your code in a debugger while your service is running and, more importantly, contacting Microsoft about this issue. By all accounts, it appears that you’ve satisfied all of the functional dependencies correctly, the system returns everything that it should for a successful run, and yet your program is still entering an undefined state with no clear remedy in sight. That’s a bug.
A usable workaround in the meantime is to use Haskell FFI to define your service architecture in another language (for example, C++) and hook into your code by either (a) exposing your Haskell code to your service layer or (b) exposing your service code to Haskell. In both cases, here’s a starting reference to use for building your service.
I wish I could have done more here (I honestly, legitimately tried), but even this much should significantly help you in getting this working.
Best of luck to you. It looks like you have a rather large number of people interested in your results.