Yesterday I made a minimal working example of calling C from Haskell, where I call a simple C function to compute the greatest common denominator, or “GCD”. The Haskell portion only serves as a wrapper around the C function. This post is a brief look at the whole setup.
The Setup
I used ghc
8.0.1, and gcc
5.4.0. 1
Folder Structure
2017-04-02-calling-c-from-haskell
├── build.sh
├── c
│ ├── gcd.c
│ └── gcd.h
└── hs
├── ffi.hs
└── GCD.hs
2 directories, 5 files
File Contents
gcd.h
[GitHub]
[Download]
#include "gcd.h"
/* Find greatest common divisor. */
int gcd(int m, int r)
{
if (r == 0)
return m;
return gcd(r, m % r);
}
gcd.c
[GitHub]
[Download]
module GCD where
import Foreign
import Foreign.C.Types
foreign import ccall "gcd"
c_gcd :: CInt -> CInt -> CInt
f_gcd :: Int -> Int -> Int
f_gcd a b = fromIntegral $ c_gcd (fromIntegral a) (fromIntegral b)
GCD.hs
[GitHub]
[Download]
ffi.hs
[GitHub]
[Download]
build.sh
[GitHub]
[Download]
Discussion
To compile the example, run the build.sh
script.
Here is the expected output of the built executable:
$ ./hs/ffi
4
15
12
.
The gcd()
C function is easy to work with because it is a pure function without side effects.
You can run the ffi
binary against valgrind
to make sure that we are not leaking any memory (sample output below).
$ valgrind --error-exitcode=1 --leak-check=yes ./hs/ffi
==14582== Memcheck, a memory error detector
==14582== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==14582== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==14582== Command: ./hs/ffi
==14582==
==14582== Warning: set address range perms: large range [0x4200000000, 0x14200100000) (noaccess)
4
15
12
==14582==
==14582== HEAP SUMMARY:
==14582== in use at exit: 0 bytes in 0 blocks
==14582== total heap usage: 48 allocs, 48 frees, 60,006 bytes allocated
==14582==
==14582== All heap blocks were freed -- no leaks are possible
==14582==
==14582== For counts of detected and suppressed errors, rerun with: -v
==14582== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Caveats
Below are some things I tried, but could not get to work.
- I tried to delete the
gcd.c
file by moving the function definition ingcd.c
togcd.h
(and deletegcd.c
entirely). I compiled the object file withgcc -c -Wall -Wextra -Werror -o gcd.o gcd.h
but then I got this error:
$ ghc --make ffi.hs ../c/gcd.o
[1 of 2] Compiling GCD ( GCD.hs, GCD.o )
[2 of 2] Compiling Main ( ffi.hs, ffi.o )
Linking ffi ...
../c/gcd.o: file not recognized: File format not recognized
collect2: error: ld returned 1 exit status
`cc' failed in phase `Linker'. (Exit code: 1)
- In
GCD.hs
you can see the lineforeign import ccall "gcd.h gcd"
. Instinctively I thought that thegcd.h
in"gcd.h gcd"
served as a kind of disambiguator, for where thegcd()
function came from. So then I defined another function namedgcd()
in a different C header file (gcd_other.h
), compiled it separately, but got a “multple definition” error:
$ ghc --make ffi.hs ../c/gcd.o ../c/gcd_other.o
[1 of 2] Compiling GCD ( GCD.hs, GCD.o )
[2 of 2] Compiling Main ( ffi.hs, ffi.o )
Linking ffi ...
../c/gcd_other.o: In function `gcd':
gcd_other.c:(.text+0x0): multiple definition of `gcd'
../c/gcd.o:gcd.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
`cc' failed in phase `Linker'. (Exit code: 1)
The version of gcc should not matter at all – actually, any decent C compiler should work.↩︎