A Haskell package can include C source code. For example, consider a simple
one-package Stack project named c-example
, created by stack new c-example
but with these changes:
A C header file my-library.h
added in new directory include
:
#ifndef MY_LIBRARY_HEADER
#define MY_LIBRARY_HEADER
int max(int, int);
#endif
A C source code file my-library.c
added in new directory c-source
:
#include "my-library.h"
/* Function returning the larger of two integers */
int max(int x1, int x2) {
if (x1 > x2)
return x1;
else
return x2;
}
A different Haskell module in source file src/Lib.hs
, including a Haskell
foreign import declaration making use of the C max
function:
module Lib ( c_max ) where
foreign import ccall "max" c_max :: Int -> Int -> Int
A different Haskell module in source file app/Main.hs
, making use of the
Haskell function c_max
exported from module Lib
:
module Main ( main ) where
import Lib ( c_max )
main :: IO ()
main = print $ c_max 10 100
The package's package.yaml
file (simplied), used to create the package's
Cabal file, might look like this:
spec-version: 0.36.0
name: c-example
version: 0.1.0.0
extra-source-files:
- include/my-library.h
dependencies:
- base >= 4.7 && < 5
library:
source-dirs: src
include-dirs: # Where to look for C header files?
- include
c-sources: # What C source code files to be compiled and linked?
- c-source/my-library.c
executables:
c-example-exe:
main: Main.hs
source-dirs: app
ghc-options:
- -threaded
- -rtsopts
- -with-rtsopts=-N
dependencies:
- c-example
The project's stack.yaml
file only needs to identify a snapshot:
snapshot: lts-22.28 # GHC 9.6.6
This example project can be built with Stack in the normal way (stack build
),
and the built executable can then be executed in the Stack environment in the
normal way (stack exec c-example-exe
).
A Haskell package can include an executable which has a main
function written
in C. For example, consider a simple one-package Stack project named
c-example
, with:
A package.yaml
describing a library and two executables, named haskell-exe
and c-exe
:
spec-version: 0.36.0
name: c-example
version: 0.1.0.0
dependencies: base
library:
source-dirs: src
# The Lib_stub.h header must be put by GHC somewhere where Cabal can find it.
# This tells GHC to put it in the autogen-stubs directory of the project
# directory.
ghc-options:
- -stubdir autogen-stubs
executables:
haskell-exe:
main: Main.hs
source-dirs: app
ghc-options:
- -threaded
- -rtsopts
- -with-rtsopts=-N
dependencies: c-example
c-exe:
main: main.c
source-dirs: c-app
ghc-options: -no-hs-main
# This specifies that directory autogen-stubs should be searched for header
# files.
include-dirs: autogen-stubs
dependencies: c-example
!!! warning
`Cabal-3.12.0.0`, a boot package of GHC 9.10.1, ignores `source-dirs` when
the `main` file is not a Haskell source code file. This was a regression and
fixed in subsequent versions of Cabal (the library).
A Haskell module souce file named Lib.hs
in directory src
:
module Lib
( myMax -- Exported only for the use of the 'Haskell' executable
) where
myMax :: Int -> Int -> Int
myMax x1 x2 = if x1 > x2 then x1 else x2
foreign export ccall myMax :: Int -> Int -> Int
A Haskell module source file named Main.hs
in directory app
:
module Main ( main ) where
import Lib ( myMax )
main :: IO ()
main = print $ myMax 10 100
A C source file named main.c
in directory c-app
:
// Based in part on
// https://downloads.haskell.org/ghc/latest/docs/users_guide/exts/ffi.html#using-your-own-main
#include <stdio.h> // Provides printf()
#include <HsFFI.h> // Provides hs_init() and hs_exit(). See the Haskell 2010
// Report, 8.7.
// Parts specific to GHC
#ifdef __GLASGOW_HASKELL__
#include "Lib_stub.h" // Automatically generated by GHC, given use of
// foreign export ... in module Lib.hs ...
#endif
int main(int argc, char *argv[]) {
// Initialises the Haskell system and provides it with the available command
// line arguments
hs_init(&argc, &argv);
// Use our foreign export from module Lib.hs ...
printf("%lld\n", myMax(10,100));
// De-initialise the Haskell system
hs_exit();
return 0;
}
The foreign export
declaration in Haskell module Lib
will cause GHC to
generate a 'stub' C header file named Lib_stub.h
. The GHC option -stubdir
will cause GHC to put that file in the specified directory (autogen-stubs
, in
this example).
!!! info
If GHC's `-stubdir` option is omitted, GHC will put the generated C header
file together with the other build artefacts for the module. However, that
location cannot be specified reliably using the `include-dirs` key.
That generated C header file will have content like:
#include <HsFFI.h>
#if defined(__cplusplus)
extern "C" {
#endif
extern HsInt myMax(HsInt a1, HsInt a2);
#if defined(__cplusplus)
}
#endif
The include-dirs
key will cause the specified directory (again,
autogen-stubs
in this example) to be searched for C header files.
The project's stack.yaml
file only needs to identify a snapshot:
snapshot: lts-22.28 # GHC 9.6.6
This example project can be built with Stack in the normal way (stack build
),
and the built executables can then be executed in the Stack environment in the
normal way (stack exec haskell-exe
for the 'Haskell' executable and
stack exec c-exe
for the 'C' executable).