5

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

関数のインスタンスで苦労しています。誰かが私を助けることができますか?

4

1 に答える 1

6

FFI を含む関数でできることは 2 つあります。 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]。これは、2 つの異なる値を同じ型にマーシャリングすることができなくなったことを意味します。たとえば、両方を持つことはできなくなりました。

instance FFI Int CInt where

instance FFI Int32 CInt where

この理由は、freeFFI定義したとおりに使用できないためです。ffitype だけから選択するインスタンスを決定する方法はありません。freeFFI :: ffitype -> basic -> IO ()または、タイプを, or (better?)に変更することもできますfreeFFI :: ffitype -> IO basic。そうすれば、ファンデプスはまったく必要ありません。

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]。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 = wrapFnunwrapFn「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

前と同じように、これらの関数をポリモーフィックにすることはできません。これが、このシステムに関する私の最大の懸念につながります。関数の種類ごとに個別のラッパーとインスタンスを作成する必要があるため、オーバーヘッドが大きくなります。多くの関数のマーシャリングを行っていない限り、努力する価値があるとは思えません。

これで関数をマーシャリングできますが、呼び出しコードで関数を使用できるようにしたい場合はどうすればよいでしょうか。この別のプロセスは関数をエクスポートしており、必要なもののほとんどは既に開発されています。

エクスポートされた関数には、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

残念ながら、この設定は引数が 1 つの関数でしか機能しません。すべての機能をサポートするために、別のクラスを作成しましょう。

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 は必要ないと思うので、それを削除して、1 つの基本タイプを複数の ffitype にマップできるようにすることができます。そうする理由の 1 つは、すべてのサイズの int を Integer にマップすることですが、toFFI実装には損失が伴います。

[2] 少し単純化。String -> String関数を の FFI タイプにマーシャリングできますCString -> IO CString。しかし、戻り値の型に IO があるため、CString -> IO CString関数を元に戻すことはできません。String -> String

于 2010-07-28T00:20:46.043 に答える