在Haskell中为FFI调用自动转换类型 [英] Automatic conversion of types for FFI calls in Haskell

查看:133
本文介绍了在Haskell中为FFI调用自动转换类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

  { - #LANGUAGE MultiParamTypeClasses,FunctionalDependencies,TypeSynonymInstances # - } 
模块ExportFFI其中

导入外部
导入Foreign.C


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

实例FFI字符串CString其中
toFFI = newCString
fromFFI = peekCString
freeFFI = free

我在为函数实例挣扎。有人可以帮我吗?

解决方案

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



您的FFI类有助于编组,创建一个如何编组函数的示例实例。



这是未经测试的,但它编译,我希望它能工作。首先,让我们稍微改变一下类:

  class FFI basic ffitype |基本 - > ffitype,ffitype  - >基本在哪里
toFFI :: basic - > IO ffitype
fromFFI :: ffitype - > IO basic
freeFFI :: ffitype - > IO()

这表示给定basic或ffitype的类型,是固定的[1]。这意味着不可能将两个不同的值编组成相同的类型,例如,你不能再同时拥有这两个元素:

 实例FFI Int CInt其中

实例FFI Int32 CInt其中

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

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

  foreign import ccallwrapper
mkIntFn ::(Int32 - > Int32) - > IO(FunPtr(Int32 - > Int32))

foreign import ccalldynamic
dynIntFn :: FunPtr(Int32 - > Int32) - > (Int32 - > Int32)

实例FFI(Int32 - > Int32)(FunPtr(Int32 - > Int32))其中
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→IOcb)→> (a - > 10 b)
unwrapFn fn a =括号(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 ccallwrapper
mkStrFn ::(CString - > IO CString) - > IO(FunPtr(CString - > IO CString))

foreign import ccalldynamic
dynStrFn :: FunPtr(CString - > IO CString) - > (CString - > IO CString)

instance FFI(String - > IO String)(FunPtr(CString - > IO CString))其中
toFFI = mkStrFn。 wrapFn
fromFFI = return。 unwrapFn。 dynStrFn
freeFFI = freeHaskellFunPtr

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



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

导出的函数必须具有可编组类型,就像 FunPtr 秒。我们可以简单地重复使用 wrapFn 来做到这一点。要导出一些函数,你只需要用 wrapFn 包装它们并导出包装的版本:

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

f2 :: String - >字符串
f2 =反向

f3 :: String - > IO Int
f3 =返回。长度

国外出口ccall f1Wrapped :: CInt - > IO CInt
f1Wrapped = wrapFn(return。f1)

国外出口ccall f2Wrapped :: CString - > IO CString
f2Wrapped = wrapFn(return。f2)

国外导出ccall f3Wrapped :: CString - > IO CInt
f3Wrapped = wrapFn f3

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

  class ExportFunction a b其中
exportFunction :: a - > b

实例(FFI a ca,FFI b cb)=> ExportFunction(a-> b)(ca - > IO cb)其中
exportFunction fn =(wrapFn(return .fn))

实例(FFI a ca,FFI b cb, FFI d cd)=> ExportFunction(a-> b-> d)(ca-> cb-> IO cd)其中
exportFunction fn = \ca cb - >做
a < - fromFFI ca
b < - fromFFI cb
toFFI $ fn ab

现在我们可以对带有1和2个参数的函数使用 exportFunction

  f4 :: Int  - > Int  - > Int 
f4 =(+)

f4Wrapped :: CInt - > CInt - > IO CInt
f4Wrapped = exportFunction f4

国外出口ccall f4Wrapped :: CInt - > CInt - > IO CInt

f3Wrapped2 = :: CString - > IO CInt
f3Wrapped2 = exportFunction f3

国外出口ccall f3Wrapped2 :: CString - > IO CInt
f3Wrapped2 = exportFunction f3

现在您只需编写 ExportFunction 自动将任何函数转换为适当的导出类型。我认为如果不使用某种类型的预处理器或unsafePerformIO,这是最好的。

[1]从技术上讲,我不认为有任何需要对于基本 - > ffitypefundep,所以你可以删除它以使一个基本类型映射到多个ffitypes。这样做的一个原因是将所有大小的整数映射到整数,但 toFFI 实现将是有损的。



[2]略微简化。你可以编组一个函数 String - >字符串添加到 CString - >的FFI类型中。 IO CString 。但是现在你不能转换 CString - > IO CString 函数回到 String - > String 因为返回类型中的IO。


I have defined the following module to help me with FFI function export:

{-# 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

I'm struggling with the instance for functions. Can someone help me?

解决方案

There are two things you can do with functions involving the FFI: 1) Marshalling: this means converting a function to a type that can be exported through the FFI. This accomplished by FunPtr. 2) Exporting: this means creating a means for non-Haskell code to call into a Haskell function.

Your FFI class helps with marshalling, and first I create a few sample instances of how to marshal functions.

This is untested, but it compiles and I expect it would work. First, let's change the class slightly:

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

This says that given the type of either "basic" or "ffitype", the other is fixed[1]. This means it's no longer possible to marshal two different values to the same type, e.g. you can no longer have both

instance FFI Int CInt where

instance FFI Int32 CInt where

The reason for this is because freeFFI can't be used as you've defined it; there's no way to determine which instance to select from just the ffitype. Alternatively you could change the type to freeFFI :: ffitype -> basic -> IO (), or (better?) freeFFI :: ffitype -> IO basic. Then you wouldn't need fundeps at all.

The only way to allocate a FunPtr is with a "foreign import" statement, which only works with fully instantiated types. You also need to enable the ForeignFunctionInterface extension. As a result the toFFI function, which should return an IO (FunPtr x), can't be polymorphic over function types. In other words, you'd need this:

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

for every different function type you want to marshal. You also need the FlexibleInstances extension for this instance. There are a few restrictions imposed by the FFI: every type must be a marshallable foreign type, and the function return type must be either a marshallable foreign type or an IO action which returns a marshallable foreign type.

For non-marshallable types (e.g. Strings) you need something slightly more complex. First of all, since marshalling happens in IO you can only marshal functions that result in an IO action. If you want to marshal pure functions, e.g. (String -> String), you need to lift them to the form (String -> IO String).[2] Let's define two helpers:

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)

These convert the types of functions to the appropriate marshalled values, e.g. wrapStrFn :: (String -> IO String) -> (CString -> IO CString); wrapStrFn = wrapFn. Note that unwrapFn uses "Control.Exception.bracket" to ensure the resource is freed in case of exceptions. Ignoring this you could write unwrapFn fn = toFFI >=> fn >=> fromFFI; see the similarity to wrapFn.

Now that we have these helpers we can start to write instances:

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

As before, it's not possible to make these functions polymorphic, which leads to my biggest reservation about this system. It's a lot of overhead because you need to create separate wrappers and instances for each type of function. Unless you're doing a lot of marshalling of functions, I would seriously doubt it's worth the effort.

That's how you can marshal functions, but what if you want to make them available to calling code? This other process is exporting the function, and we've already developed most of what's necessary.

Exported functions must have marshallable types, just like FunPtrs. We can simply re-use the wrapFn to do this. To export a few functions all you need to do is wrap them with wrapFn and export the wrapped versions:

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

Unfortunately this setup only works for single-argument functions. To support all functions, let's make another class:

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

Now we can use exportFunction for functions with 1 and 2 arguments:

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

Now you just need to write more instances of ExportFunction to automatically convert any function to the appropriate type for exporting. I think this is the best you can do without either either using some type of pre-processor or unsafePerformIO.

[1] Technically, I don't think there's any need for the "basic -> ffitype" fundep, so you could remove it to enable one basic type to map to multiple ffitypes. One reason to do so would be to map all sized ints to Integers, although the toFFI implementations would be lossy.

[2] A slight simplification. You could marshal a function String -> String to the FFI type of CString -> IO CString. But now you can't convert the CString -> IO CString function back to String -> String because of the IO in the return type.

这篇关于在Haskell中为FFI调用自动转换类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆