2012-02-14 17 views
7

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ć.

+0

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'). –

+0

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! –

+0

@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

Odpowiedz

9

Jeśli chcesz uzyskać dostęp do struktury Haskella, takiej jak MVar, która nie ma funkcji bibliotecznej do przekształcenia jej w reprezentację wskaźnika (co oznacza, że ​​nie powinna być przekazywana do C), musisz wykonać funkcję częściowej aplikacji .

W aplikacji funkcji częściowej, polega na tym, aby zbudować funkcję częściową z już zastosowaną MVar i przekazać wskaźnik do tej funkcji do C. C następnie wywoła ją z powrotem do obiektu, aby umieścić MVar. Przykładowy kod poniżej (wszystkie poniżej kod pochodzi od czegoś, co zrobił wcześniej - I zmodyfikowane go do przykładów tutaj, ale nie testowane modyfikacje):

-- this is the function that C will call back 
syncWithC :: MVar CInt -> CInt -> IO() 
syncWithC m x = do 
       putMVar m x 
       return() 

foreign import ccall "wrapper" 
    syncWithCWrap :: (CInt -> IO()) -> IO (FunPtr (CInt -> IO())) 

main = do 
    m <- newEmptyMVar 
    -- create a partial function with mvar m already applied. Pass to C. C will back with CInt 
    f <- syncWithCWrap $ syncWithC m 

Co zrobić, jeśli obiekt MVar jest bardziej skomplikowane? Następnie musisz zbudować trwałą instancję obiektu MVar, jeśli nie istnieje.Na przykład, jeśli chcę użyć MVar z tablicy pary Ints, to najpierw zdefiniować Storable wystąpienie par int (SV jest Storable Vector, MSV jest Storable Mutable Vector):

data VCInt2 = IV2 {-# UNPACK #-} !CInt 
        {-# UNPACK #-} !CInt 

instance SV.Storable VCInt2 where 
    sizeOf _ = sizeOf (undefined :: CInt) * 2 
    alignment _ = alignment (undefined :: CInt) 
    peek p = do 
      a <- peekElemOff q 0 
      b <- peekElemOff q 1 
      return (IV2 a b) 
    where q = castPtr p 
    {-# INLINE peek #-} 
    poke p (IV2 a b) = do 
      pokeElemOff q 0 a 
      pokeElemOff q 1 b 
    where q = castPtr p 
    {-# INLINE poke #-} 

Teraz można po prostu przejść wskaźnik do wektora do C, niech zaktualizuje wektor i wywoła funkcję pustą bez żadnych argumentów (ponieważ C już wypełnia wektor). To również uniknąć kosztownego rozrządowych danych poprzez dzielenie pamięci między Haskell i C.

-- a "wrapper" import is a converter for converting a Haskell function to a foreign function pointer 
foreign import ccall "wrapper" 
    syncWithCWrap :: IO() -> IO (FunPtr (IO())) 


-- call syncWithCWrap on syncWithC with both arguments applied 
-- the result is a function with no arguments. Pass the function, and 
-- pointer to x to C. Have C fill in x first, and then call back syncWithC 
-- with no arguments 
syncWithC :: MVar (SV.Vector VCInt2) -> MSV.IOVector VCInt2 -> IO() 
syncWithC m1 x = do 
       SV.unsafeFreeze x >>= putMVar m1 
       return() 

Na stronie C, trzeba deklarację struct dla VCInt2 tak, że wie, jak je analizować:

/** Haskell Storable Vector element with two int members **/ 
typedef struct vcint2{ 
    int a; 
    int b; 
} vcint2; 

Tak po stronie C podajemy ten wskaźnik vcint2 dla obiektu MVar.