4
votes

I'm using the Haskell FFI with a C library that defines a number of struct types containing members which are pointers to doubles, intended to be treated as arrays of doubles:

typedef struct Foo {
  int length;
  double* values;
} FooT;

In my Haskell bindings to this library I have an equivalent data type in which I'm trying to use Data.Vector.Storable.Vector Double for the array:

data Foo = Foo {
  length :: Int,
  values :: Data.Vector.Storable.Vector Double
} deriving (Show, Eq)

In order to marshall data between the C library and my Haskell code, of course, I have to write Storable instances for these types. I'm trying to work out a way of using Data.Vector.Storable.unsafeFromForeignPtr to create Haskell Vectors from the double* arrays that the C library has allocated and populated on the heap. I'm hoping that by doing this I can avoid copying the contents of the double* arrays and just have the Vector as a kind of wrapper over the array. (Side question would be: given that the double* arrays can be up to 10,000s of doubles, is it worth pursuing this non-copying?)

This is what I have so far. I'm using hsc2hs macros to help generate the Storable peek implementation:

instance Storable Foo where
  alignment _ = alignment (undefined :: CDouble)
  sizeOf _ = #{size FooT}

  peek ptr = do
    len <- (#peek FooT, length) ptr
    valuesField <- ((#peek FooT, values) ptr) :: IO (ForeignPtr Double)
    let values' = DV.unsafeFromForeignPtr0 valuesField len

    return Foo { length = len, values = values' }

  poke ptr (Foo len values') = do
    (#poke FooT, length) ptr len
    DV.unsafeWith values' (\ptrValues -> (#poke FooT, values) ptr ptrValues)

So in my peek I'm trying to #peek the values member as a ForeignPtr Double which I could then use with unsafeFromForeignPtr. However, #peek generates code like this:

valuesField <- (((\ hsc_ptr -> peekByteOff hsc_ptr 16)) ptr) :: IO (ForeignPtr Double)

and gets stuck because there's no Storable instance for ForeignPtr Double. I think if I tried to implement an instance for ForeignPtr Double I would just be commuting the problem of how to access the address value of a struct member to the peek implementation for that instance.

So in summary, how can I access an address value (i.e. pointer) struct member in such a way that I can use it as an argument to unsafeFromForeignPtr?

2

2 Answers

1
votes

I don't know about using hsc2hs but you already have a pointer to the data in peek so you just use that, with the proper offset of course. Disclaimer: this compiles, but is untested.

import Data.Vector.Storable (Vector, unsafeFromForeignPtr)
import Foreign.Storable (Storable (..))
import Foreign.C.Types 
import Foreign.ForeignPtr (newForeignPtr_)
import Foreign.Ptr 

instance Storable Foo where
  peek ptr = do 
    len <- peek (castPtr ptr)
    valsPtr <- newForeignPtr_  (castPtr ptr `plusPtr` (sizeOf (undefined :: CInt)))
    return $ Foo len $ unsafeFromForeignPtr valsPtr 0 len  
0
votes

Using user2407038's suggestion of newForeignPtr_ the working solution to this is:

instance Storable Foo where
  alignment _ = alignment (undefined :: CDouble)
  sizeOf _ = #{size FooT}

  peek ptr = do
    len'    <- fmap fromIntegral (((#peek FooT, len) d) :: IO CInt)

    valuesField <- ((#peek FooT, values) ptr) :: IO (Ptr Double)
    valuesPtr   <- newForeignPtr_ valuesField
    let values' = DV.unsafeFromForeignPtr0 valuesField len

    return Foo { length = len, values = values' }

I found that just #peeking the int field was giving me garbage whereas explicitly declaring it to be CInt and then using fromIntegral to convert that to an Int gives me the right value. And then I actually have a correct length to use with unsafeFromForeignPtr. I've then done a similar thing with the double* pointer too: explicitly declare it to be Ptr Double.

In my actual application some of the structs also have char* type field which, similar to the int fields, I found were also just coming out as garbage after the #peek. Again, explicitly declaring their types to be CString solved that too.