简体   繁体   中英

haskell FFI passing array of C structs in and out

I am trying to perform a series of transforms on graphical files using Haskell and Repa/DevIL. The starting example used was provided by the Haskell wiki page https://wiki.haskell.org/Numeric_Haskell:_A_Repa_Tutorial . I am an imperative programmer of 30 years experience with some erlang for good measure, trying to learn Haskell outside a classroom environment.

The problem is manipulating the data after the file load was first transformed into a Repa array:

import Data.Array.Repa.IO.DevIL (runIL,readImage,writeImage,Image(RGB),IL)
import qualified Data.Array.Repa as R
import Data.Vector.Unboxed as DVU
import Control.Monad

main :: IO ()
main = do
  [f] <- getArgs
  (RGB a) <- runIL $ Data.Array.Repa.IO.DevIL.readImage f
  let
    c = (computeP (R.traverse a id rgbTransform)) :: IL (Array U DIM3 Float)

which is successfully cast to type "Array F DIM3 Float" as output from the rgbTransform. From that point on it has been a nightmare to use the data. Flicking the array storage type between F(oreign) and U(nboxed) changes all following call's usability, plus the Repa-added monad layer IL forces use of liftM for nearly every equation following the 1st transform:

  let -- continued
     sh = liftM R.extent c -- IL DIM3
     v = liftM R.toUnboxed c -- IL (Vector Float)
     lv = liftM DVU.length v -- IL Int
     f = liftM indexed v -- vector of tuples: (Int,a) where Int is idx
     k = (Z :. 2) :. 2 :. 0 :: DIM3

These are the routines I can call without error. The IO monad's print command produces no output if placed in or after this 'let' list, due to the IL monad layer.

The game plan for the curious:

  1. read the graphic file (done, via Repa)
  2. resize image (not done, no resize in Repa, must be hand-coded)
  3. transform and convert image from Word8 to Float (done)
  4. get a Stablepointer to the transformed Float data (not done)
  5. transform in-place the Float data as an array of C structs of {Float a,b,c;}, by an external C routine via FFI (not completely done). This is done hopefully without marshalling a new graphic array by passing a pointer to the data
  6. perform more passes over the transformed data to extract more info (partly done).

I am looking for help with issues 4 and 5.

4 -> The type system has been difficult to deal with while attempting to get C-usable memory pointers. Going thru the mountains of haskell library calls has not helped.

5 -> The external C routine is of type:

foreign import ccall unsafe "transform.h xform"
c_xform :: Ptr (CFloat,CFloat,CFloat) ->
           CInt ->
           IO ()

The Ptr is expected to point to an unboxed flat C array of rgb_t structs:

typedef struct
{
    float r;
    float g;
    float b;
} rgb_t;

Available web-based FFI descriptions of how to deal with array pointers in FFI are non-existent if not downright obscure. The fairly straightforward idea of unfreezing and passing in a C array of floating-point RGB structs, modifying them in-place and then freezing the result is what I had in mind. The external transform is pure in the sense that the same input will produce predictable output, does not use threads, does not use global vars nor depend upon obscure libraries.

Foreign.Marshal.Array seems to provide a way to convert haskell data to C data and other way around.

I tested interfacing C code and haskell using the following files (Haskell + FFI for the first time for me)

hsc2hs rgb_ffi.hsc
ghc main.hs rgb_ffi.hs rgb.c

rgb.h

#ifndef RGB_H
#define RGB_H

#include <stdlib.h>

typedef struct {
    float r;
    float g;
    float b;
} rgb_t;

void rgb_test(rgb_t * rgbs, ssize_t n);

#endif

rgb.h

#include <stdlib.h>
#include <stdio.h>
#include "rgb.h"

void rgb_test(rgb_t * rgbs, ssize_t n)
{
    int i;

    for(i=0; i<n; i++) {
        printf("%.3f %.3f %.3f\n", rgbs[i].r, rgbs[i].g, rgbs[i].b);
        rgbs[i].r *= 2.0;
        rgbs[i].g *= 2.0;
        rgbs[i].b *= 2.0;
    }
}

rgb_ffi.hsc

{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE CPP                      #-}

module RGB where

import Foreign
import Foreign.C
import Control.Monad (ap)

#include "rgb.h"

data RGB = RGB {
      r :: CFloat, g :: CFloat, b :: CFloat
} deriving Show

instance Storable RGB where
    sizeOf    _ = #{size rgb_t}
    alignment _ = alignment (undefined :: CInt)

    poke p rgb_t = do
      #{poke rgb_t, r} p $ r rgb_t
      #{poke rgb_t, g} p $ g rgb_t
      #{poke rgb_t, b} p $ b rgb_t

    peek p = return RGB
             `ap` (#{peek rgb_t, r} p)
             `ap` (#{peek rgb_t, g} p)
             `ap` (#{peek rgb_t, b} p)

foreign import ccall "rgb.h rgb_test" crgbTest :: Ptr RGB -> CSize -> IO ();

rgbTest :: [RGB] -> IO [RGB]
rgbTest rgbs = withArray rgbs $ \ptr ->
               do
                 crgbTest ptr (fromIntegral (length rgbs))
                 peekArray (length rgbs) ptr

rgbAlloc :: [RGB] -> IO (Ptr RGB)
rgbAlloc rgbs = newArray rgbs

rgbPeek :: Ptr RGB -> Int -> IO [RGB]
rgbPeek rgbs l = peekArray l rgbs

rgbTest2 :: Ptr RGB -> Int -> IO ()
rgbTest2 ptr l =
    do
      crgbTest ptr (fromIntegral l)
      return ()

main.hs

module Main (main) where

import RGB

main =
 do
    let a = [RGB {r = 1.0, g = 1.0, b = 1.0},
             RGB {r = 2.0, g = 2.0, b = 2.0},
             RGB {r = 3.0, g = 3.0, b = 3.0}]
    let l = length a
    print a
    -- b <- rgbTest a
    -- print b

    c <- rgbAlloc a
    rgbTest2 c l
    rgbTest2 c l
    d <- rgbPeek c l
    print d
    return ()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM