Skip to content

Instantly share code, notes, and snippets.

@larsr
Last active April 29, 2021 02:34
Show Gist options
  • Save larsr/03bd6a4201d57aa13e42b144089f7bb0 to your computer and use it in GitHub Desktop.
Save larsr/03bd6a4201d57aa13e42b144089f7bb0 to your computer and use it in GitHub Desktop.

Creating a .so dynamic library of a haskell program and calling it from C

TLDR:

  • Export a haskell function with foreign export.
  • Create a C wrapper that calls hs_init when the library is loaded.
  • Compile the .hs and .c wrapper with ghc, linking with lHSrts-ghc8.6.5
  • Use it from C by linking as usual.

(For more details, see here.)

Haskell file

Create a haskell file that exports the function, in this case the haskell function c_eval is exported as eval.

-- This file is Eval.hs.
module Eval() where

import Foreign.C
import Foreign

foreign export ccall "eval" c_eval :: CString -> Ptr CInt -> IO (Ptr CInt)

c_eval s r = do
    cs <- peekCAString s
    case hs_eval cs of
        Nothing -> return nullPtr
        Just x -> do
            poke r x
            return r

hs_eval :: String -> Maybe CInt
hs_eval s = Just (read s)

C Wrapper

Create a C wrapper with a constructor and destructor.

/* This file is hsbracket.c. */
#include <HsFFI.h>

static void my_enter(void) __attribute__((constructor));
static void my_enter(void)
{
  static char *argv[] = { "libEval.so", 0 }, **argv_ = argv;
  static int argc = 1;
  hs_init(&argc, &argv_);
}

static void my_exit(void) __attribute__((destructor));
static void my_exit(void)
{
  hs_exit();
}

Building the .so file

Compile this to get the file libEval.so. (I use stack to handle dependencies. If you don't, just do ghc -O2 -dynamic ... instead)

stack ghc -- -O2 -dynamic -shared -fPIC -o libEval.so Eval.hs hsbracket.c -lHSrts-ghc8.6.5

Using the library from C

Now you want to use eval() in a C program. Note that we need to declare eval's type.

/* This file is  eval.c */
#include <stdio.h>

/* declare eval */
int *eval(char* n, int* d);

int main(int argc, char** argv) {
  int d = 0;

  if(argc>1) {
   /* call eval */
    eval(argv[1], &d);
  }

  printf("d = %d\n", d);
  return 0;
}

Compile eval.c and link it with libEval.so. Note that gcc must know where to find the library, so we specify the search path with -L

gcc eval.c -L. -lEval -o eval

or you can compile it with ghc which i nice because then you can use the Eval_stub.h for the function prototype:

ghc -- -no-hs-main  eval.c  -L. -lEval -o eval

To run it, the dynamic linker must also know where to find libEval.so.

LD_LIBRARY_PATH=. ./eval 543

Compiling into a static binary

If this is eval.c

/* This file is  eval.c */
#include <HsFFI.h>
#ifdef __GLASGOW_HASKELL__
#include "Eval_stub.h"
#endif
#include <stdio.h>

int main(int argc, char** argv) {
  hs_init(&argc, &argv);
  int d = 0;

  if(argc>1) {
   /* call eval */
    eval(argv[1], &d);
  }

  printf("d = %d\n", d);
  hs_exit();
  return 0;
}

we build a dynamic binary with

stack ghc -- --make -no-hs-main -optc-O eval.c Eval.hs -o eval

or a static binary with

stack ghc -- --make -no-hs-main -optc-O -optl-static eval.c Eval.hs -o eval
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment