Saturday, December 31, 2011

FFI in Haskell, How to Call Impure C Functions Without Pointers

I started off writing this little parallel port toy program in the Haskell programming language. It didn't seem too ambitious and I knew how to implement this directly in C so it seemed like a good project to try.

The first thing I needed to do was wrap up a few C functions to get inputs and outputs from the parallel ports. These three functions in linux are inb(2), outb(2), iopl(2) and ioperms(2). I opted to wrap up only inb, outb and iopl.

These functions are defined by sys/io.h on Linux. They are defined as static functions so I need to wrap them up in a small C file to create an object file with symbols for haskell to use them easily. Here's the wrapper code that places these functions in the hs_ namespace.
/* Copyright 2011 Zac Slade and released under the GNU Public License version 2 or later */
#include <sys/io.h>

int hs_iopl(int perm)
{
return iopl(perm);
}

unsigned char hs_inb(unsigned short int port)
{
return inb(port);
}

void hs_outb(unsigned char value, unsigned short int port)
{
outb(value,port);
}


I found a lot of good Haskell Foreign Function Interface(FFI) intruductions, but they all showed something more complex than what I was using. Or they wrapped up functions like sin(3) that pretends to have no side effects like pure functions in Haskell.

I ended up making the functions map to c_inb, c_outb and c_iopl to make it easier on me to know when I was using a C function wrapped up by Haskell. Here's how I defined those three functions using the boiler plate code required by the Haskell FFI.

{-# LANGUAGE ForeignFunctionInterface -}
{- Copyright 2011 Zac Slade and licensed under the GNU Public License version 2 or later -}
module ParaPortLinuxIO where
import Foreign.C
import Data.Bits

-- This file implements a wrapper interface for inb/outb and iopl all found in sys/io.h on Linux
-- Due to the nature of the include file and the definition of these symbols a small C wrapper
-- was needed and the functions were placed into the hs_ namespace.

-- Type aliases for use with this module
type CByte = CUChar
type Port = CUShort
type SomeBits = [Int]
type IOPorts = [Port]

-- int iopl(int perm)
foreign import ccall unsafe "hs_iopl"
c_iopl :: CInt -> IO CInt

-- unsigned char inb(unsigned short int port)
foreign import ccall unsafe "hs_inb"
c_inb :: Port -> IO CByte

-- void outb(unsigned char value, unsigned short int port)
foreign import ccall "hs_outb"
c_outb :: CByte -> Port -> IO ()


These functions have no illusions about being pure. As you might have noticed if you are already a Haskeller the functions all operate in the IO Monad to inform the programmer that these should not under any circumstances be treated like pure functions. Also in the above definition we see that both c_iopl and c_inb are marked as unsafe as well. This conveys even more information to the programmer alerting them to the fact that these functions read values from the real world and require state to be carried along with the values.

As you can see above it really doesn't take much code to actually wrap up C library function (even functions defined as static functions). From the above two snippets you can begin using inb(2), outb(2) and iopl(2) in your own Haskell programs.

Happy Haskell Hacking!

No comments: