Skip to content

Latest commit

 

History

History
229 lines (180 loc) · 5.83 KB

haskell_and_c_code.md

File metadata and controls

229 lines (180 loc) · 5.83 KB

Haskell and C code

Haskell packages with C code

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).

Haskell packages with C main function

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).