Има две неща, които можете да направите с функции, включващи 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 действие, което връща маршалируем чужд тип.
За немаршалируеми типове (напр. Strings) ви трябва нещо малко по-сложно. Първо, тъй като маршалингът се случва в 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“, така че можете да го премахнете, за да позволите на един основен тип да се съпостави с множество ffitypes. Една от причините да го направите би била да картографирате всички int-ове с размери към Integers, въпреки че имплементациите 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