我目前正在尝试将部分Windows API移植到Haskell。其中一个函数是SetConsoleCtrlHandler
,它注册一个处理程序回调,只要从控制台输入收到Ctrl + C或Ctrl + Break,就会调用它。所以这里有一个小样本程序:
{-# LANGUAGE CPP #-}
{-# LANGUAGE ForeignFunctionInterface #-}
module Main where
import Control.Monad
import GHC.ConsoleHandler
import Foreign
import System.Win32.Process
import System.Win32.Types
foreign import ccall "wrapper"
makeHandlerPtr :: (DWORD -> IO BOOL) -> IO (FunPtr (DWORD -> IO BOOL))
foreign import stdcall "windows.h SetConsoleCtrlHandler"
c_SetConsoleCtrlHandler :: FunPtr (DWORD -> IO BOOL) -> BOOL -> IO BOOL
main :: IO ()
main = unsafeCtrl
unsafeCtrl :: IO ()
unsafeCtrl = do
ptrHandler <- makeHandlerPtr $ \d -> do
putStrLn $ "received event " ++ show d
return True
c_SetConsoleCtrlHandler ptrHandler True
forM_ [1..50] $ \_ -> sleep 100
freeHaskellFunPtr ptrHandler
safeCtrl :: IO ()
safeCtrl = do
installHandler $ Catch $ \e -> do
putStrLn $ "received event " ++ show e
forM_ [1..50] $ \_ -> sleep 100
如果您通过ghc --make AsyncCallback.hs
编译上述程序并运行程序,一旦收到控件事件,它就会崩溃并出现以下错误:
$ AsyncCallback
AsyncCallback.exe: schedule: re-entered unsafely.
Perhaps a 'foreign import unsafe' should be 'safe'?
但是,添加-threaded
选项进行编译似乎工作正常:
$ AsyncCallback
received event 0
received event 1
received event 0
不确定为什么这会让它发挥作用,因为我没有明确地在这里使用线程,而且我无法看到GHC会隐含地开始一个新线程。
早些时候在#haskell上问过这个问题,有人指出从C回调异步调用Haskell函数本质上是不安全的,上面的例子或多或少巧合。我还指出GHC.ConsoleHandler.installHandler
似乎是SetConsoleCtrlHandler
的安全包装,并且用main = safeCtrl
运行上述程序确实可以正常工作。我试图理解installHandler
(C-side和Haskell-side)的实现,但我还是不太明白。
这里的整个问题似乎是回调是异步发出的,因此Haskell RTS没有准备好#34;当回调试图运行Haskell代码时。所以我想知道
installHandler
的GHC实施如何运作?使实施工作和我的版本失败的关键部分是什么?