我想用.NET gui创建一个Haskell应用程序。我想使用cabal作为我的构建工具来利用它的包管理等。我认为Haskell部分应该是调用.NET代码的可执行文件:
这样可以避免手动初始化Haskell RTC,如下所述:http://www.haskell.org/ghc/docs/7.0.3/html/users_guide/win32-dlls.html
cabal无法轻松生成Windows dll:http://www.haskell.org/haskellwiki/Cabal/Developer-FAQ#Building_DLLs__with_Cabal
我发现创建一个使用hs-dotnet调用.NET的Haskell可执行文件相当容易,但是我还需要我的GUI代码才能回调到Haskell。我希望使用Haskell的“外部导出”命令来实现这一点,然后通过.NET native interop调用这个导出的函数。但是,“外部导出”功能似乎并没有在可执行文件中创建一个入口点,当我对生成的可执行文件执行dumpbin /EXPORTS
时,我看不到入口点。我不确定这是否是因为GHC只在通过-shared
开关创建dll时创建了入口点,或者cabal是否添加了一个禁止创建入口点的标志。
所以我想问题是如何强制GHC在我的Windows可执行文件中创建入口点?或者我会更好地使用.NET可执行文件,通过必要的步骤创建一个带有cabal的Haskell dll并手动初始化Haskell RTC?
答案 0 :(得分:4)
我通常通过将回调作为函数指针传递来解决这个问题。例如,我有一个应用程序,按下按钮需要回调Haskell(我使用的是Cocoa,但名称非常相似)。
首先,我将NSButton对象子类化,并为我的新ButtonC类提供类型为void(*onClickCallback)()
的私有成员。我还声明了一个函数void setClickCallback(ButtonC *button, void(*callback)());
实现你的子类,这样无论何时单击该按钮,都会调用函数指针。在.Net中,可能有一种聪明的方式与代表这样做(自从我使用.Net以来已经有一段时间了。)
接下来,我的Haskell绑定看起来像这样(省略了一些代码):
module Foreign.Button where
data ButtonObj
type ButtonPtr = ForeignPtr ButtonObj
foreign import ccall unsafe "setClickCallback"
c_setClickCallback :: Ptr ButtonObj -> FunPtr (IO ()) -> IO ()
foreign import ccall "wrapper"
makeClickCallback :: IO () -> IO (FunPtr (IO ()))
buttonSetCallback :: ButtonPtr -> FunPtr (IO ()) -> IO ()
buttonSetCallback btn cb =
withForeignPtr btn $ \p -> c_setClickCallback p cb
buttonNew :: IO ButtonPtr
buttonNew = ...
现在,IO ()
类型的Haskell数据可以包装在FunPtr
中,并通过buttonSetCallback
传递到GUI中。只要按下按钮,就会执行IO ()
操作。
createButton :: IO ButtonPtr
createButton = do
btn <- buttonNew
let buttonClicked = print "The button was clicked!"
btnCallback <- makeClickCallback buttonClicked
buttonSetCallback btn btnCallback
return btn
要注意FunPtr
的一件事是它们不是垃圾收集的。您需要在完成后手动取消分配它们,否则您将发生内存泄漏。一个好的做法是永远不要共享FunPtr
,也永远不要在Haskell方面保留对它们的引用。这样,您的对象可以释放FunPtr
作为其清理的一部分。这需要对Haskell(一个freeFunPtr
函数)进行另一次回调,该回调应该在所有对象之间共享,并且只有在可执行文件终止时才会释放。