hsc2hs: Mutate a C struct with Haskell

2020-06-20 05:43发布

问题:

I am trying to write a Haskell program that communicates with C (ultimately for iOS via GHC-iOS). I want it to pass a string from C to Haskell, have Haskell process it and then return some Data types from Haskell to C Structs via hsc2s. I have been unsuccessful at finding a clear, simple tutorial. The only thing Haskell needs from C is the String, nothing else.

I have no trouble with the very first part, passing a string to Haskell.

testPrint :: CString -> IO ()
testPrint c = do 
    s <- peekCString c
    putStrLn s

For test purposes and future reference, I just want to be able to handle something like the following.

C Struct

struct testdata {
   char *a;
   char *b;
   int c;
};

Haskell Data Type

data TestData =  TestData {
  a :: String,
  b :: String,
  c :: Int
} deriving Show

-- not sure what the type would look like
testParse :: CString -> TestData

I understand that I need to typeclass TestData as Storable and implement peek, poke, sizeOf and alignment, but I need to see a simple example before I can really understand it. Most of the tutorials require outside libraries and make it more complicated than it needs to be.

Here are the resources I've looked at:

Stackoverflow - How to use hsc2hs to bind to constants, functions and data structures?

Haskell Cafe - FFI for a beginner

Writing Haskell interfaces to C code: hsc2hs

Haskell Wikibook - FFI

Edit: Currently where I am stuck (gets segmentation error when setFoo is called in C)

Haskell Code Snippet

instance Storable Foo where
  sizeOf = #{size foo}
  alignment = alignment (undefined :: CString)
  poke p foo = do
     #{poke foo, a} p $ a foo
     #{poke foo, b} p $ b foo
     #{poke foo, c} p $ c foo
  peek p = return Foo
    `ap` (#{peek foo, a} p)
    `ap` (#{peek foo, b} p)
    `ap` (#{peek foo, c} p)

foreign export ccall "setFoo" setFoo :: Ptr Foo -> IO ()

setFoo :: Ptr Foo -> IO ()
setFoo f = do
  newA <- newCString "abc"
  newB <- newCString "def"
  poke f (Foo newA newB 123)

C Code Snippet

foo *f;
f = malloc(sizeof(foo));
foo->a = "bbb"
foo->b = "aaa"
foo->c = 1
// this is incorrect
// setFoo(&f);
// this is correct
setFoo(f);

回答1:

This is a complete example of a C program that passes a struct to Haskell then Haskell mutates the values of that struct. It has no outside dependencies. You should be able to copy the code and run it if you have GHC. Hopefully this will serve as a simple, straightforward example for others.

Foo.h

typedef struct {
    char *a;
    char *b;
    int   c;
} foo;

Foo.c

#include "foo.h"

HsFoo.hsc

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

module HsFoo where

import Foreign
import Foreign.C

import Control.Applicative
import Control.Monad

#include "foo.h"

data Foo = Foo { 
    a :: CString
  , b :: CString
  , c :: Int
} deriving Show

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

    poke p foo = do
        #{poke foo, a} p $ a foo
        #{poke foo, b} p $ b foo
        #{poke foo, c} p $ c foo

    peek p = return Foo
              `ap` (#{peek foo, a} p)
              `ap` (#{peek foo, b} p)
              `ap` (#{peek foo, c} p)

foreign export ccall "free_HaskellPtr" free :: Ptr a -> IO ()
foreign export ccall "setFoo" setFoo :: Ptr Foo -> IO ()
setFoo :: Ptr Foo -> IO ()
setFoo f = do
  newA <- newCString "abc"
  newB <- newCString "def"
  poke f $ Foo newA newB 3
  return ()

main.c

#include <stdio.h>
#include <stdlib.h>
#include "HsFoo_stub.h"
#include "foo.h"

int main(int argc, char *argv[]) {  
  hs_init(&argc, &argv);

  foo *f;
  f = malloc(sizeof(foo));
  f->a = "Hello";
  f->b = "World";
  f->c = 55555; 

  printf("foo has been set in C:\n  a: %s\n  b: %s\n  c: %d\n",f->a,f->b,f->c);

  setFoo(f);

  printf("foo has been set in Haskell:\n  a: %s\n  b: %s\n  c: %d\n",f->a,f->b,f->c);

  free_HaskellPtr(f->a);
  free_HaskellPtr(f->b);
  free(f);
  hs_exit();
}

Command Line - Compile files and run

$ hsc2hs HsFoo.hsc
$ ghc -c HsFoo.hs foo.c
$ ghc -no-hs-main foo.c HsFoo.o main.c -o main
$ ./main