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

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

我正在为函数的实例而苦苦挣扎。有人能帮我吗?

最佳答案

您可以对涉及 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 ()

这表示给定“basic”或“ffitype”的类型,另一个是固定的[1]。这意味着不再可能将两个不同的值编码为同一类型,例如你不能再同时拥有
instance FFI Int CInt where

instance FFI Int32 CInt where

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

分配 FunPtr 的唯一方法是使用“外部导入”语句,该语句仅适用于完全实例化的类型。您还需要启用 ForeignFunctionInterface延期。结果toFFI函数,它应该返回一个 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

对于您想要编码的每种不同的函数类型。您还需要 FlexibleInstances此实例的扩展名。 FFI 施加了一些限制:每个类型都必须是可编码的外部类型,并且函数返回类型必须是可编码的外部类型或返回可编码的外部类型的 IO 操作。

对于不可编码的类型(例如字符串),您需要稍微复杂一些的东西。首先,由于编码发生在 IO 中,因此您只能编码导致 IO 操作的函数。
如果要编码纯函数,例如(String -> String),你需要将它们提升为 (String -> IO String) 形式。[2]让我们定义两个助手:
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 .请注意 unwrapFn使用“Control.Exception.bracket”来确保在发生异常时释放资源。忽略这个你可以写 unwrapFn fn = toFFI >=> fn >=> fromFFI ;查看与 wrapFn 的相似性。

现在我们有了这些助手,我们可以开始编写实例了:
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

和以前一样,不可能使这些函数成为多态的,这导致我对这个系统最大的保留。这是很多开销,因为您需要为每种类型的函数创建单独的包装器和实例。除非您对函数进行大量编码,否则我会严重怀疑这样做是否值得。

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

导出的函数必须具有可编码类型,就像 FunPtr s。我们可以简单地重复使用 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

现在我们可以使用 exportFunction对于具有 1 个和 2 个参数的函数:
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

现在你只需要写更多 ExportFunction 的实例自动将任何函数转换为适当的类型以进行导出。我认为这是最好的方法,无需使用某种类型的预处理器或 unsafePerformIO。

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

[2] 稍微简化。你可以编码一个函数 String -> StringCString -> IO CString的FFI类型.但是现在你不能转换 CString -> IO CString功能返回 String -> String因为返回类型中的 IO。

关于haskell - Haskell 中 FFI 调用的类型自动转换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3338940/

相关文章:

haskell - 非多态函数的多态签名 : why not?

haskell - 声明一个具有完整定义的子类

haskell - 在什么情况下可以/不可以拥有数据类型的 Functor 实例?

c - Haskell FFI/C MPFR 库包装问题

rust - 将数据和指向该数据的可变指针存储在结构中是否安全?

haskell - 为 FFI 定位 Javascript 库

debugging - :type in ghci in a ghc source file? 最接近的等价物是多少

haskell - 安装ghc-mod时遇到问题

haskell - 你如何实现 "show"尾递归?

Haskell 即席多态性