3
votes

Suppose you have a C API provides that provides a C struct

typedef struct A {
    int i;
    float f;
} A;

and a function that populates it:

void getA(A* a);

For example, this could be a getter for some information from the innards of the C API.

In Haskell the C struct would be mirrored by

data A = A {
    i :: Int,
    f :: Float
}

The Storable instance is

instance Storable A where
    sizeOf    _ = {#sizeof  A #}
    alignment _ = {#alignof A #}
    peek p = ...
    poke p x = ...

Peek and poke are business as usual with the {#get...#} and {#set #} pragmas processed by c2hs.

The Haskell function getA :: IO A should be something like

{#fun unsafe getA as getA {alloca- `A' peek*} -> `()'#}

except this doesn't work because c2hs creates this binding:

foreign import ccall unsafe "include/A.h getA"
    _getA'_ :: Ptr () -> IO ()

It has Ptr () as first argument. This can be sorted out by

{#fun unsafe getA as getA {allocaA- `A' peekA*} -> `()'#}

peekA :: Ptr () -> IO A
peekA = peek . castPtr

allocaA :: (Ptr () -> IO a) -> IO a
allocaA f = alloca $ \(p :: Ptr A) -> f (castPtr p)

The allocaA is important because it ensures that memory for an A is allocated instead of memory for just a () if alloca were used.

Although this works it is somewhat tedious and you have guaranteed segfaults if you ever forget to write allocaXYZ instead of just alloca. (I just saw a good amount of time being spent on tracking down one such error.)

I expected to find a {#fun...#} incantation that produces

foreign import ccall unsafe "include/A.h getA"
    _getA'_ :: Ptr A -> IO ()

from which all else would follow naturally (note the Ptr A instead of Ptr ()). But as far as I can tell there's only the {allocXYZ- 'XYZ' peekXYZ*} route.

So the question is: Can this be done in a better way using c2hs?

1

1 Answers

1
votes

Are you using {#pointer ...#} hooks? Since you're not treating A like an opaque pointer (i.e. i and f are accessible normally and the Storable instance is written explicitly), you'll want to use the arrow form:

{#pointer *A as APtr -> A#}

At that point, you still have to use the alloca/peek functions to marshal A, but your first, ideal {#fun ...#} hook should work as written. (You wind up ignoring the APtr type; it shows up in the generated code, but it's not necessary in the *.chs file.)

Also note that you need to add that pointer definition to every file A is used in; even if you export APtr from one primary file, you still need to add {#pointer *A as APtr -> A nocode#} everywhere you use it.