15
votes

I have a question about the Haskell C FFI, specifically about accessing static data structures exported by a C library.

The C library I’m wrapping has static data structures like FOO_GEORGE below, exported in the following fashion:

static struct foo_struct foo_table[] = { /* list of foo structs */ }
typedef struct foo_struct *foo_t;
foo_t FOO_GEORGE = &foo_table[0];
foo_t FOO_HARRY  = &foo_table[1];
foo_t FOO_SUSAN  = &foo_table[2];
/* ... */

The value I need in my Haskell library is the address of the foo_struct (&foo_table[n]), that is, the contents of FOO_GEORGE, which is put in an opaque newtype wrapper in the usual way (the constructors are not exported from the library, only the type):

newtype Foo = Foo { getFoo :: (Ptr Foo) }

This is what I’m doing now:

foreign import ccall "&FOO_GEORGE" fooGeorgeHandle :: Ptr (Ptr Foo)
FooGeorge = Foo . unsafeDupablePerformIO . peek $ fooGeorgeHandle

I think this is an appropriate use of unsafePerformIO, since the C API and implementation say that this use of peek is pure and has no side effects. Also, I believe that I don’t need to take any of the precautions outlined in bullet points in the documentation (starting with {-# NOINLINE foo #-}).

My overall question is: am I doing this right? Are the bits of analysis above correct? Is there a better or preferable way to do this? If the foreign import clause allowed me to do the pointer defererence, that would be nice, but it doesn’t seem to; am I missing anything? One could argue that this would be a bad feature since it could segfault if the pointer is bad — but then, the same is true of the peek I have to use instead, so it comes to the same thing.

Thanks!

1
The unsafeDupablePerformIO should be fine in this instance. Can you use the CApiFFI extension? haskell.org/ghc/docs/7.6.2/html/users_guide/ffi.htmlJohn L
I didn't know about CApiFFI (or anything else documented there), but it looks promising. I'll try it out and report back. Thanks!Richard E. Silverman
I guess I should've made an answer, but I've never used CApiFFI so I wasn't sure it would work.John L
Please consider answering your own question, instead of editing the question.askmish
Did you ever get a chance to write up the workaround for ghci?Edward Kmett

1 Answers

2
votes

John L. suggested the CApiFFI extension, which does exactly what I want: allows you to import values rather than locations. Now:

{-# LANGUAGE CApiFFI #-}
newtype {-# CTYPE "foo.h" "struct foo_struct" #-} Foo = Foo { getFoo :: (Ptr Foo) }
foreign import capi "foo.h value FOO_GEORGE" fooGeorgePtr :: Ptr a
fooGeorge = Foo fooGeorgePtr

Another advantage is that this works regardless of whether FOO_GEORGE is a C variable or a preprocessor macro. The C API I’m working with uses both, and different implementations of the same API that I link to do so differently, so being independent of that is great.

However, there’s a stumbling block: CApiFFI doesn’t work with ghci! The problem is known, and it isn’t slated to be fixed until GHC 8.0.1 (when I first wrote this, it was due for 7.10, and then was pushed forward). I have a very hacky workaround. CApiFFI works by generating a C library on the fly, compiling and linking the Haskell program against it. It removes the library when done; the ghci problem seems to be that the .so file is gone by the time ghci needs to link against it. I just grab the .c file during compilation before it gets deleted, then compile it myself and tell ghci to load it. Since I don’t change that portion of the program very often, it works out for me well enough.

My method for catching the temporary .c file is to start ghci in Emacs compilation-mode, with (setq compilation-auto-jump-to-first-error t). Emacs sees the error and loads the .c file into a buffer before GHC gets around to deleting it — by the time I see it the file is gone, but I’ve got the contents in a buffer.

Update: ghci -fobject-code Foo works, but can only see the names exported from the module.