2012-02-14 36 views
7

Câu hỏi của tôi là về cách viết Giao diện Haskell thân thiện mô hình các cuộc gọi lại có thể được gọi từ mã C. Tuy nhiên, các cuộc gọi lại được giải quyết ở đây (HaskellWiki), tôi tin rằng câu hỏi này phức tạp hơn ví dụ từ liên kết đó.Gọi lại FFI Haskell với Tiểu bang

Giả sử chúng ta có mã C, đòi hỏi callbacks và header trông giống như sau:

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

int execution(CallbackType* caller); 

Trong trường hợp này các chức năng execution mất một chức năng gọi lại và sẽ sử dụng để xử lý dữ liệu mới, thực chất là đóng cửa. Các cuộc gọi trở lại hy vọng một chuỗi đầu vào, một bộ đệm đầu ra đã được phân bổ với kích thước outputMaxSize và con trỏ userData, mà có thể được đúc tuy nhiên bên trong gọi lại.

Chúng tôi làm những điều tương tự trong haskell, khi chúng tôi vượt qua xung quanh đóng cửa với MVars, vì vậy chúng tôi vẫn có thể giao tiếp. Vì vậy, khi chúng tôi viết giao diện nước ngoài, chúng tôi muốn giữ loại này.

Cụ thể ở đây là những gì Bộ luật FFI có thể trông giống như:

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 

Người dùng sẽ có thể làm được việc này, nhưng nó cảm thấy như một giao diện nghèo từ họ cần phải viết callbacks với loại PTR(). Thay vào đó, chúng tôi muốn thay thế điều này bằng MVars mà cảm thấy tự nhiên hơn. Vì vậy, chúng tôi muốn viết một hàm:

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

Để chuyển đổi sang C, chúng tôi muốn có một chức năng như:

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

main = wrap_callBack (castCallback myCallback) >>= execute 

Trong trường hợp này castCallback là dành cho hầu hết các phần không khó thực hiện, chuyển đổi chuỗi -> cstring, Int -> CInt và sao chép trên chuỗi đầu ra.

Tuy nhiên, phần cứng đang giải quyết MVar thành Ptr, không nhất thiết phải lưu trữ được.

Câu hỏi của tôi là cách tốt nhất để viết về mã gọi lại trong Haskell, cái nào vẫn có thể được liên lạc với.

+0

Tôi không phải là một chuyên gia về FFI, nhưng sự hiểu biết của tôi là những người C đã sử dụng thủ thuật 'void * 'vì họ không có đóng cửa thực sự. Trong Haskell, chúng ta có các bao đóng thực sự - vì vậy chỉ cần để đối số 'void *' ra khỏi giao diện Haskell hoàn toàn và đóng trên bất kỳ dữ liệu cục bộ nào (có lẽ là 'IORef' hoặc' MVar') thông qua một phần ứng dụng. –

+0

Ahh! Gotcha. Tôi sẽ thử. Tôi nghĩ rằng đó là những gì ràng buộc có thể đã được thực hiện, nhưng tôi đã không bắt được điều đó. Cảm ơn vì sự trả lời! –

+0

@tigger, tôi đã làm thủ thuật tương tự trước đó DanielWagner đề xuất cho cuộc gọi đồng bộ vào Haskell từ C - có được một chức năng một phần bằng cách áp dụng đối số MVar và để hàm C gọi lại với dữ liệu cho MVar. Nếu MVar của bạn phức tạp hơn, thì bạn có thể sử dụng vector Storable hoặc một thể hiện có thể lưu trữ để truyền dữ liệu đến MVar từ C. Chuyển một Ptr đến Storable instance tới C. Ví dụ ở đây: http://hpaste.org/63702 – Sal

Trả lời

9

Nếu bạn muốn truy cập cấu trúc Haskell như MVar không có hàm thư viện để chuyển đổi nó thành biểu diễn con trỏ (nghĩa là nó không được chuyển đến C), thì bạn cần thực hiện một phần ứng dụng hàm .

Trong ứng dụng hàm một phần, mẹo là xây dựng một hàm một phần với MVar đã được áp dụng, và chuyển con trỏ tới hàm đó tới C. C sau đó sẽ gọi nó trở lại với đối tượng để đưa vào MVar. Một mã ví dụ dưới đây (tất cả các mã dưới đây có nguồn gốc từ một cái gì đó tôi đã làm trước đây - tôi sửa đổi nó cho ví dụ ở đây nhưng đã không kiểm tra những sửa đổi):

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

gì nếu đối tượng MVAr của bạn là phức tạp hơn? Sau đó, bạn cần xây dựng một cá thể Storable của đối tượng MVar nếu nó không tồn tại.Ví dụ, nếu tôi muốn sử dụng một MVAr với mảng của cặp Ints, thì trước tiên xác định một trường hợp Storable các cặp Int (SVStorable Vector, MSVStorable 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 #-} 

Bây giờ, bạn chỉ có thể vượt qua một trỏ tới vectơ tới C, để nó cập nhật vectơ và gọi lại hàm void không có đối số (vì C đã điền vào vectơ). Điều này cũng tránh dữ liệu đắt marshalling bằng cách chia sẻ bộ nhớ giữa Haskell và 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() 

Về phía C, bạn sẽ cần một tuyên bố struct cho VCInt2 để nó biết làm thế nào để phân tích nó:

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

Vì vậy, , ở phía C, bạn đang chuyển nó vcint2 con trỏ cho đối tượng MVar.

Các vấn đề liên quan