Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vector (Vector Foo) -> (Ptr (Ptr Foo) -> IO a) -> IO a?

I am making a simple wrapper for a c library that needs to have a list of vectors passed to it. It takes an array of pointers to arrays. To make a nice interface I'd like to have Vector (or list) of Vectors, but I can't really find out how to do this in idiomatic haskell. (Or any other way than memcopying stuff around).

What I'm looking for is something like

Vector (Vector Foo) -> (Ptr (Ptr Foo) -> IO a) -> IO a  
like image 580
aleator Avatar asked Jun 29 '11 07:06

aleator


2 Answers

Since you're passing to a C function, you should use Data.Vector.Storable. You can't just pass a vector of vectors of Storable because that will not be a vector merely of pointers to arrays, it also includes size and offset information.

if the argument to the C function is, say, int myCFunc(foo** arrays, int sz) then the following code should work:

import Data.Vector.Storable
import Foreign.Storable
import Foreign.ForeignPtr

withCFunction :: Storable a =>         -- ^ Storable so compatible with C
                Vector (Vector a)      -- ^ vector of vectors
              -> (Ptr (Ptr a) -> IO b) -- ^ C function wrapped by FFI
              -> IO b
withCFunction v f = do
  vs <- mapVectorM (\x -> let (fp,_,_) = unsafeToForeignPtr x 
                          in unsafeForeignPtrToPtr fp) v
  mapVectorM_ (\x -> let (tfp,_,_) = unsafeToForeignPtr x
                     in touchForeignPtr tfp) vs
  let (vfp,_,_) =  unsafeToForeignPtr vs
  withForeignPtr vfp $ \p -> f p
like image 126
vivian Avatar answered Oct 05 '22 13:10

vivian


Edit: hCsound doesn't deal with this exact case, so I've added a full example below.

You might want to look at my package hCsound (darcs repo), which has to deal with a very similar case.

Note that it's very important that the C library doesn't modify the arrays used by a Data.Vector.Storable.Vector. If you do need to modify the data, you should copy the old data first, modify the array through the ffi, and finally wrap the pointers into a new Vector.

Here's the code. As was pointed out in a comment, Data.Vector.Storable.Vector doesn't have a Storable instance itself, so you'll need the outer vector to be a Data.Vector.Vector.

import Foreign.Storable
import Foreign.Ptr
import Foreign.ForeignPtr
import Foreign.Marshal.Array

import qualified Data.Vector as V
import qualified Data.Vector.Storable as S
import Data.Vector.Storable.Internal

withPtrArray v f = do
  let vs = V.map S.unsafeToForeignPtr v   -- (ForeignPtr, Offset, Length)
  ptrV = V.toList $ V.map (\(fp,off,_) -> offsetToPtr fp off) vs
  res <- withArray ptrV f
  V.mapM_ (\(fp,_,_) -> touchForeignPtr fp) vs
  return res

Note the array is allocated by withArray, so it's automatically gc'd after the function returns.

These arrays aren't null-terminated, so you'll need to make sure that the length is passed to the C function by some other method.

withForeignPtr isn't used. Instead, touchForeignPtr is called to ensure that the ForeignPtr's aren't deallocated before the C function is finished. In order to use withForeignPtr, you'd need to nest calls for each internal vector. That's what the nest function in the hCsound code does. It's rather more complicated than just calling touchForeignPtr.

like image 35
John L Avatar answered Oct 05 '22 13:10

John L