2012-02-14 23 views
7

Soruma C kodundan çağrılan geri aramaları modelleyen arkadaş canlısı Haskell Arayüzleri nasıl yazılır. Geri aramalar burada (HaskellWiki) adreslenir, ancak bu sorunun bu bağlantıdan alınan örnekten daha karmaşık olduğuna inanıyorum.FFI Haskell Geri Arama Durumu

geri aramalar gerektiren biz C kodu olduğunu varsayalım ve başlık aşağıdaki gibidir: işlevi execution bir geri çağırma işlevi alır ve yeni veri, esasen bir kapatma işlemek için kullanacaktır Bu durumda

typedef int CallbackType(char* input, char* output, int outputMaxSize, void* userData) 

int execution(CallbackType* caller); 

. Geri arama, bir giriş dizgisi, outputMaxSize boyutunda tahsis edilmiş bir çıktı tamponu ve geri çağırma içinde dökülebilen userData işaretçisini bekler.

Haskell'de benzer şeyler yapıyoruz, MVars ile kapaklar etrafından geçtiğimizde, iletişim kurabiliyoruz. Bu yüzden Yabancı arayüzü yazdığımızda, bu tür bir tür tutmak isteriz. Özellikle burada

FFI Kodu gibi görünebilir şudur:

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 

Kullanıcılar bu tür bir şey yapmak mümkün olmalıdır, ancak bunlar tip PTR ile geri aramalar yazmaya gerek beri fakir bir arayüzü gibi hissediyor(). Bunun yerine daha doğal olan MVars ile değiştirmek isteriz. Yani bir fonksiyon yazmak istiyorum:

myCallback :: String -> Int -> MVar a -> (Int, String) 
myCallback input maxOutLength data = ... 

gibi bir işlevi var, biz istiyoruz C'ye dönüştürmek amacıyla: castCallback çoğunlukla bu durumda

castCallback :: (String -> Int -> MVar a -> (Int, String)) 
      -> (CString -> CString -> CInt -> Ptr() -> IO CInt) 

main = wrap_callBack (castCallback myCallback) >>= execute 

uygulamak zor değil, dizgiyi dönüştür -> cstring, Int -> CInt ve çıktı dizesinin üzerine kopyala. Bununla birlikte, zor kısım MVar'ı Ttr ile Ttr olarak, yani depolanmaya uygun olmayan bir şekilde çözmektedir.

Benim sorum, Haskell'de geri çağrılabilir kod yazmak için en iyi yoldur, bu da yine de iletilebilir.

+0

Hiçbir şekilde bir FFI uzmanı değilim, fakat benim anlayışım C milletinin “void *” hilesini kullanmasıydı çünkü gerçek kapanışları yok. Haskell'de, gerçek kapanışlarımız var - bu nedenle, Haskell arabiriminden 'void *' argümanını kısmi uygulama yoluyla tüm yerel verilerin (belki de bir "IORef" veya "MVar") tamamen ve kapalı olarak bırakın. –

+0

Ahh! Yakaladım. Bir deneyeyim. Bence bu bağlamanın yapmış olabileceği şeydi, ama ben bunu anlamadım. Cevabınız için teşekkürler! –

+0

@tigger, DanielWagner'ın C'den Haskell'e senkronize geri çağrılması için önerdiği aynı hile yaptım - MVar argümanını uygulayarak kısmi bir işlev elde edin ve C fonksiyonunun MVar için verilerle geri çağırmasına izin verin. MVar'ınız daha karmaşıksa, verileri saklamak için Storable vektörünü veya depolanabilir bir örneği kullanabilirsiniz. Verileri C'den MVar'a geçirebilirsiniz. Ptr - Storable örneği C'ye geçirin. Burada bir örnek: http://hpaste.org/63702 – Sal

cevap

9

MVar gibi bir Haskell yapısına erişmek istediğiniz bir kütüphane işlevine sahip olmayan bir işaretçi temsiline dönüştürmek istiyorsanız (yani C'ye iletilmesi gerekli değildir), kısmi işlevli uygulama yapmanız gerekir .

Kısmi işlev uygulamasında, hile zaten uygulanan MVar ile kısmi bir işlev oluşturmak ve işaretçiyi bu işleve C olarak geçirmektir. Daha sonra MVar'a yerleştirmek için nesneyi tekrar çağırır. senin Mvar nesne daha karmaşıktır

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

Ya: - Aşağıdaki örnek kodu (burada örnekler için modifiye ancak modifikasyonları test etmedim Aşağıdaki tüm kod önce yaptığım bir şey türetilmiştir)? Öyleyse, yoksa MVar nesnesinin Storable örneğini oluşturmanız gerekir.I Ints çiftinin bir dizi ile bir Mvar kullanmak isteyen Örneğin, ilk önce (MSVStorable Mutable Vector olup, SVStorable Vector olan) Orta çiftleri Storable örneğini tanımlamak:

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 #-} 

Şimdi, sadece bir geçebilir vektörü C'ye işaret eder, vektörü günceller ve hiçbir argüman olmadan boşluk fonksiyonunu geri çağırırlar (çünkü C zaten vektörü doldurur). o bilir ki bu da C tarafında Haskell ve 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() 

arasında hafızayı paylaşarak pahalı veri sıralanırken önlemek, bunu ayrıştırmak nasıl VCInt2 için yapı beyanı gerekir: Yani

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

C tarafında, MVar nesnesi için vcint2 işaretçisini geçiriyorsunuz.