Testing FFI Code (with “foreign import”s) with GHC

2020-08-17 18:21发布

问题:

Good (your local time of day), everyone.

I went through Real World Haskell's chapter on the Foreign Function Interface, and did some follow-up reading here. I'm now experimenting with binding to C functions, and I'd like some clarification on some things.

The following is fairly clear:

foreign import ccall unsafe "math.h sin" c_sin :: CDouble -> CDouble

I can load this and code that uses it in ghci, and everything is fine. It even loads in the embedded ghci in emacs's Haskell mode. I find this great for testing. math is a system library so this is straight-forward.

Now an example from Real World Haskell:

foreign import ccall unsafe "pcre.h pcre_compile" c_pcre_compile :: ...

I've left out the rest of the function signature on purpose. Now, I can't load this in Haskell mode. All the examples I've seen say this must be done:

ghci -lpcre

Which I do, and get instant confirmation that things are loading properly:

GHCi, version 7.6.2: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Loading object (dynamic) /usr/lib/gcc/x86_64-unknown-linux-gnu/4.7.2/../../../../lib/libpcre.so ... done
final link ... done

I can then load my binding code and test away, but...

_Question 1_ Can I load non-system libraries from within ghci, like pcre? This would allow me to test within emacs.

Moving on. Things get more complicated when I try to write bindings to my own C code.

foreign import ccall unsafe "myprint.h myprint" c_myprint :: CString -> IO ()

Admittedly a rather pointless function. It takes a ByteString from Haskell and prints it with C. Here is a simple test file:

{-# LANGUAGE ForeignFunctionInterface #-}
-- printTest.hs

import Foreign
import Foreign.C.Types
import Foreign.C.String

import qualified Data.ByteString.Char8 as B

---

foreign import ccall unsafe "myprint.h myprint" c_myprint :: CString -> IO ()

---

main = B.useAsCString (B.pack "Tempura is great!") c_myprint

I have been able to compile this by doing:

ghc --make myprint.c printTest.hs

And get an executable, but I haven't been able to load it in ghci at all. This severely lags the testing process.

_Question 2_ What do I have to do to load Haskell code in ghci that binds to my C code? None of the major sources of FFI information had anything to say about this. No amount of fiddling with ghci -L could get it to work.

Please and thanks for any help you can offer.

回答1:

ghci will load any library so long as it's valid for your architecture and can be located on some path. On windows, pathnames with spaces used to cause problems, I don't know if they still do.

To load your own code in ghci, you need to first compile it, then tell ghci to load the output of that:

mybox$ gcc -c myprint.c
mybox$ ghci Myprint.hs myprint.o

*Main> main
Loading package array-0.4.0.1 ... linking ... done.
Loading package deepseq-1.3.0.1 ... linking ... done.
Loading package bytestring-0.10.0.2 ... linking ... done.
Tempura is great!
*Main>

You could also compile the C files into a library and load that into ghci, but for just one file using an object file is quite convenient. If you want to create a library, a command like @Jonke suggested should work. On my system (OSX),

mybox$ gcc -shared -fPIC myprint.c -o libmyprint.dylib
mybox$ ghci -L. -lmyprint Foo.hs

On my system it also works to just use the library filepath as an argument, but I don't know if that's portable.



标签: haskell ffi ghci