sir*_*usa 7 haskell asynchronous callback ffi
我目前正在尝试将部分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
Run Code Online (Sandbox Code Playgroud)
如果您通过编译上述程序ghc --make AsyncCallback.hs并运行该程序,则一旦收到控件事件,它就会崩溃,并出现以下错误:
$ AsyncCallback
AsyncCallback.exe: schedule: re-entered unsafely.
Perhaps a 'foreign import unsafe' should be 'safe'?
Run Code Online (Sandbox Code Playgroud)
但是,使用-threaded添加的选项进行编译似乎工作正常:
$ AsyncCallback
received event 0
received event 1
received event 0
Run Code Online (Sandbox Code Playgroud)
不知道为什么这会使它工作,因为我没有在这里明确使用线程,我无法看到GHC将隐式启动新线程的位置.
早些时候在#haskell上问过这个问题,有人指出从C回调中异步调用Haskell函数本质上是不安全的,上面的例子或多或少巧合.我也被指出GHC.ConsoleHandler.installHandler这似乎是一个安全的包装SetConsoleCtrlHandler,并运行上述程序main = safeCtrl确实工作正常.我试图理解installHandler(C端和Haskell端)的实现,但我不太明白.
这里的整个问题似乎是回调是异步发出的,这样当回调试图运行Haskell代码时,Haskell RTS没有"准备好".所以我想知道
installHandler运作?使实施工作和我的版本失败的关键部分是什么?