Есть две вещи, которые вы можете сделать с функциями, использующими 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
, которая должна возвращать 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. Если вы хотите маршалировать чистые функции, например. (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] Технически, я не думаю, что есть необходимость в fundep «basic -> ffitype», поэтому вы можете удалить его, чтобы один базовый тип мог сопоставляться с несколькими fftypes. Одной из причин для этого может быть сопоставление целых чисел всех размеров с целыми числами, хотя реализации toFFI
будут с потерями.
[2] Небольшое упрощение. Вы можете маршалировать функцию String -> String
в тип FFI CString -> IO CString
. Но теперь вы не можете преобразовать функцию CString -> IO CString
обратно в String -> String
из-за IO в возвращаемом типе.
person
John L
schedule
28.07.2010
String -> String
станетCString -> CString
или что-то подобное. - person Tener   schedule 28.07.2010