在Haskell中自动转换FFI调用的类型

时间:2010-07-26 20:56:19

标签: haskell typeclass ffi

我已经定义了以下模块来帮助我进行FFI功能导出:

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, TypeSynonymInstances #-}
module ExportFFI where

import Foreign
import Foreign.C


class FFI basic ffitype | basic -> ffitype where
    toFFI :: basic -> IO ffitype
    fromFFI :: ffitype -> IO basic
    freeFFI :: ffitype -> IO ()

instance FFI String CString where
    toFFI = newCString
    fromFFI = peekCString
    freeFFI = free

我正在为功能实例苦苦挣扎。有人能帮助我吗?

1 个答案:

答案 0 :(得分:6)

对于涉及FFI的功能,您可以做两件事:   1)编组:这意味着将函数转换为可以通过FFI导出的类型。这是由FunPtr完成的。   2)导出:这意味着为非Haskell代码创建一种调用Haskell函数的方法。

您的FFI类有助于编组,首先我创建了一些如何编组函数的示例实例。

这是未经测试的,但是它会编译,我希望它会起作用。首先,让我们稍微改变一下课程:

class FFI basic ffitype | basic -> ffitype, ffitype -> basic where
    toFFI :: basic -> IO ffitype
    fromFFI :: ffitype -> IO basic
    freeFFI :: ffitype -> IO ()

这表示给定“基本”或“ffitype”的类型,另一个是固定的[1]。这意味着不再可能将两个不同的值编组为相同的类型,例如你不能再兼得

instance FFI Int CInt where

instance FFI Int32 CInt where

原因是因为freeFFI不能像你定义的那样使用;没有办法确定从ffitype中选择哪个实例。或者,您可以将类型更改为freeFFI :: ffitype -> basic -> IO ()或(更好?)freeFFI :: ffitype -> IO basic。那么你根本不需要玩弄。

分配FunPtr的唯一方法是使用“外部导入”语句,该语句仅适用于完全实例化的类型。您还需要启用ForeignFunctionInterface扩展程序。因此,应该返回toFFI的{​​{1}}函数不能是函数类型的多态。换句话说,你需要这个:

IO (FunPtr x)

为您要编组的每种不同的函数类型。此实例还需要foreign import ccall "wrapper" mkIntFn :: (Int32 -> Int32) -> IO (FunPtr (Int32 -> Int32)) foreign import ccall "dynamic" dynIntFn :: FunPtr (Int32 -> Int32) -> (Int32 -> Int32) instance FFI (Int32 -> Int32) (FunPtr (Int32 -> Int32)) where toFFI = mkIntFn fromFFI = return . dynIntFn freeFFI = freeHaskellFunPtr 扩展名。 FFI强加了一些限制:每个类型必须是可编组的外来类型,函数返回类型必须是可编组的外部类型或返回可编组外来类型的IO操作。

对于非可编组类型(例如字符串),您需要稍微复杂一些的东西。首先,由于编组在IO中发生,因此您只能编组导致IO操作的函数。   如果你想编组纯函数,例如(String - > String),你需要将它们提升到表格(String - > IO String)。[2]让我们定义两个帮手:

FlexibleInstances

这些将函数类型转换为适当的编组值,例如: wrapFn :: (FFI a ca, FFI b cb) => (a -> IO b) -> (ca -> IO cb) wrapFn fn = fromFFI >=> fn >=> toFFI unwrapFn :: (FFI a ca, FFI b cb) => (ca -> IO cb) -> (a -> IO b) unwrapFn fn a = bracket (toFFI a) freeFFI (fn >=> fromFFI) 。请注意,wrapStrFn :: (String -> IO String) -> (CString -> IO CString); wrapStrFn = wrapFn使用“Control.Exception.bracket”来确保在出现异常时释放资源。忽略这一点你可以写unwrapFn;看看与wrapFn的相似性。

现在我们有了这些助手,我们可以开始编写实例了:

unwrapFn fn = toFFI >=> fn >=> fromFFI

和以前一样,不可能使这些函数具有多态性,这导致我对这个系统的最大保留。这是一个很大的开销,因为你需要为每种类型的函数创建单独的包装器和实例。除非你正在进行大量的功能编组,否则我会非常怀疑它是值得的。

这就是你可以编组函数的方法,但如果你想让它们可用于调用代码呢?另一个过程导出该功能,我们已经开发了大部分必要的功能。

导出的函数必须具有可编组类型,就像foreign import ccall "wrapper" mkStrFn :: (CString -> IO CString) -> IO (FunPtr (CString -> IO CString)) foreign import ccall "dynamic" dynStrFn :: FunPtr (CString -> IO CString) -> (CString -> IO CString) instance FFI (String -> IO String) (FunPtr (CString -> IO CString)) where toFFI = mkStrFn . wrapFn fromFFI = return . unwrapFn . dynStrFn freeFFI = freeHaskellFunPtr s一样。我们可以简单地重复使用FunPtr来执行此操作。要导出一些函数,您需要做的就是用wrapFn包装它们并导出包装版本:

wrapFn

不幸的是,此设置仅适用于单参数函数。为了支持所有功能,让我们创建另一个类:

f1 :: Int -> Int
f1 = (+2)

f2 :: String -> String
f2 = reverse

f3 :: String -> IO Int
f3 = return . length

foreign export ccall f1Wrapped :: CInt -> IO CInt
f1Wrapped = wrapFn (return . f1)

foreign export ccall f2Wrapped :: CString -> IO CString
f2Wrapped = wrapFn (return . f2)

foreign export ccall f3Wrapped :: CString -> IO CInt
f3Wrapped = wrapFn f3

现在我们可以将class ExportFunction a b where exportFunction :: a -> b instance (FFI a ca, FFI b cb) => ExportFunction (a->b) (ca -> IO cb) where exportFunction fn = (wrapFn (return . fn)) instance (FFI a ca, FFI b cb, FFI d cd) => ExportFunction (a->b->d) (ca->cb->IO cd) where exportFunction fn = \ca cb -> do a <- fromFFI ca b <- fromFFI cb toFFI $ fn a b 用于包含1和2个参数的函数:

exportFunction

现在您只需要编写更多f4 :: Int -> Int -> Int f4 = (+) f4Wrapped :: CInt -> CInt -> IO CInt f4Wrapped = exportFunction f4 foreign export ccall f4Wrapped :: CInt -> CInt -> IO CInt f3Wrapped2 = :: CString -> IO CInt f3Wrapped2 = exportFunction f3 foreign export ccall f3Wrapped2 :: CString -> IO CInt f3Wrapped2 = exportFunction f3 实例,以自动将任何函数转换为适当的导出类型。我认为如果不使用某种类型的预处理器或不安全的PerformaIO,这是你能做到的最好的。

[1]从技术上讲,我认为不需要“basic - &gt; ffitype”fundep,所以你可以删除它以使一个基本类型映射到多个ffitypes。这样做的一个原因是将所有大小的整数映射到整数,尽管ExportFunction实现将是有损的。

[2]略有简化。您可以将函数toFFI封送到FFI类型String -> String。但是现在由于返回类型中的IO,您无法将CString -> IO CString函数转换回CString -> IO CString