繁体   English   中英

在Haskell中自动转换FFI呼叫的类型

[英]Automatic conversion of types for FFI calls in Haskell

我定义了以下模块来帮助我进行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 ()

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

instance FFI Int CInt where

instance FFI Int32 CInt where

原因是因为freeFFI不能按您定义的方式使用; 无法确定仅从ffitype中选择哪个实例。 或者,您可以将类型更改为freeFFI :: ffitype -> basic -> IO () ,或(更好?) freeFFI :: ffitype -> IO basic 这样一来,您根本就不需要臀部。

分配FunPtr的唯一方法是使用“外部导入”语句,该语句仅适用于完全实例化的类型。 您还需要启用ForeignFunctionInterface扩展。 结果,应返回IO (FunPtr x)toFFI函数不能在函数类型上具有多态性。 换句话说,您将需要以下内容:

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 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一样,导出的函数必须具有可编组的类型。 我们可以简单地重新使用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]从技术上讲,我认为不需要“基本-> ffitype”基金,因此您可以删除它以使一个基本类型能够映射到多个ffitype。 这样做的原因之一是将所有大小的int映射到Integer,尽管toFFI实现会有损。

[2]略有简化。 您可以将函数String -> String编组为CString -> IO CString的FFI类型。 但是现在由于返回类型中的IO,您不能将CString -> IO CString函数转换回CString -> IO CString String -> String

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM