异步C通过FFI回调到Haskell代码

时间:2016-08-23 07:09:33

标签: 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

如果您通过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运行上述程序确实可以正常工作。我试图理解installHandlerC-sideHaskell-side)的实现,但我还是不太明白。

这里的整个问题似乎是回调是异步发出的,因此Haskell RTS没有准备好#34;当回调试图运行Haskell代码时。所以我想知道

  1. installHandler的GHC实施如何运作?使实施工作和我的版本失败的关键部分是什么?
  2. 是否有其他选项/一般模式如何从C回调中异步调用Haskell代码?在这种特殊情况下,我可以切换到GHC实施。但是我可能会遇到类似的C函数,这些函数异步调用Haskell代码,我必须自己编写一个安全的绑定。

0 个答案:

没有答案