Moje pytanie dotyczy tego, jak pisać przyjazne interfejsy Haskell, które mogą wywoływać wywołania zwrotne z kodu C. Oddzwonienia są tutaj omawiane (HaskellWiki), jednak uważam, że to pytanie jest bardziej złożone niż przykład z tego linku.Callback Haskell FFI z State
Załóżmy, że mamy kod C, wymagające wywołania zwrotne i nagłówek wygląda następująco:
typedef int CallbackType(char* input, char* output, int outputMaxSize, void* userData)
int execution(CallbackType* caller);
W tym przypadku funkcja execution
bierze funkcji zwrotnej i wykorzystać je do przeprowadzania nowych danych zasadniczo zamknięcie. Oddzwanianie oczekuje ciągu wejściowego, bufora wyjściowego, który został przydzielony o rozmiarze outputMaxSize
i wskaźnika userData, który można jednak odrzucić wewnątrz wywołania zwrotnego.
Robimy podobne rzeczy w haskell, kiedy przechodzimy wokół zamknięć z MVars, więc nadal możemy się komunikować. Dlatego kiedy piszemy interfejs zagraniczny, chcielibyśmy zachować ten typ.
Konkretnie tutaj jest to, co Kodeks FFI może wyglądać następująco:
type Callback = CString -> CString -> CInt -> Ptr() -> IO CInt
foreign import ccall safe "wrapper"
wrap_callBack :: Callback -> IO (FunPtr Callback)
foreign import ccall safe "execution"
execute :: FunPtr Callback -> IO CInt
Użytkownicy powinni być w stanie robić tego typu rzeczy, ale czuje się jak biedny interfejsu od że trzeba napisać wywołania zwrotne z typem Ptr(). Zamiast tego chcielibyśmy zastąpić to MVars , które czują się bardziej naturalne. Więc chcielibyśmy napisać funkcję:
myCallback :: String -> Int -> MVar a -> (Int, String)
myCallback input maxOutLength data = ...
W celu konwersji do C, chcielibyśmy mieć funkcję jak:
castCallback :: (String -> Int -> MVar a -> (Int, String))
-> (CString -> CString -> CInt -> Ptr() -> IO CInt)
main = wrap_callBack (castCallback myCallback) >>= execute
W tym przypadku castCallback jest w przeważającej części nie trudno wdrożyć, przekształcić ciąg -> cstring, Int -> CInt i skopiować przez ciąg wyjściowy.
Najtrudniej jest jednak rozdzielić MVar na Ptr, który niekoniecznie jest przechowywany.
Moje pytanie jest najlepszym sposobem, aby przejść do pisania kodu zwrotnego w Haskell, z którym nadal można się komunikować.
Nie jestem żadnym ekspertem od FFI, ale zrozumiałem, że ludzie z C użyli sztuczki "void *", ponieważ nie mają prawdziwych zamknięć. W Haskell mamy prawdziwe zamknięcia - więc po prostu częściowe zastosowanie zostawić argument "void *" z interfejsu Haskella całkowicie i blisko wszelkich lokalnych danych (być może 'IORef' lub' MVar'). –
Ahh! Mam cię. Dam temu szansę. Sądzę, że tak właśnie się wiązało, ale tego nie zauważyłem. Dzięki za odpowiedzi! –
@Tigger, zrobiłem tę samą sztuczkę, przed którą DanielWagner zasugerował zsynchronizowane wywołanie zwrotne do Haskella z C - uzyskać częściową funkcję przez zastosowanie argumentu MVar, i pozwolić funkcji C wywołać ją z powrotem z danymi dla MVar. Jeśli twój MVar jest bardziej skomplikowany, możesz użyć Starzonego wektora lub przechowywanej instancji, aby przekazać dane do MVar z C. Przekaż Ptr do Przechowalnej instancji do C. Przykład tutaj: http://hpaste.org/63702 – Sal