💜 Acknowledgements
Special thanks to MonoidMusician for all xyr help and support while writing this article!
1 About this article
A while ago I started learning about the Foreign Function Interface and I couldn't really find much information about it that was not really outdated or plain wrong.
The information that is correct is also spread out in small pieces over many websites, making it more difficult to understand. It can also be confusing if multiple sources say the opposite, which one is correct?
Here I want to bundle a lot of information about FFI by writing a basic application, just something that prints clickable buttons in a window.
1.1 What you should know
I assume you know what the IO-monad is, how monad transformers work and have a basic understanding of C++ (I will not explain for-loops).
1.2 What will be covered
The following topics will be covered in this article:
- Using
foreign importto call C or C++-functions (without name mangling); - Using
foreign exportto call Haskell-functions in C or C++ - How to create pointers that get freed automatically to prevent memory issues;
- How to create arrays in Haskell;
- How to create structs in Haskell (manually and via GStorable);
- How to create function pointers in Haskell;
- Why
MonadReader (IORef [a]) mis better for storing pointers thanMonadState [a] m; - The difference between
ccallandcapi.
1.3 Used technologies
The technologies used in this article are:
- GHC 9.4.7
- Cabal 3.10.3.0
- Qt 6.7.0
- GCC 14.1.1 (20240507)
2 About FizzBuzz
FizzBuzz is a challenge where you need to print some numbers, replacing numbers divisible by 3, 5 and 15 by Fizz
, Buzz
and FizzBuzz
respectively.
It is a quick test used by many job interviewers to see if their candidate has the basic knowledge of programming. You need some way to iterate, some way to check for conditions and you need to know a little bit about data types - "Fizz" is a string whereas 1 is a number.
When people write it in Haskell they often use the default console output, but that is boring. In this article we will be building FizzBuzz with a nice, colourful user interface!
2.1 Resulting application
At the end of this article we will have a colourful interface that looks like the image below. We will start with something basic, and keep adding onto it until we get to that result.
2.2 Basic implementation
Below you can find a basic implementation of FizzBuzz:
#!/usr/bin/env cabal
{- cabal:
build-depends: base
ghc-options: -Wno-tabs
-}
module Main where
main :: IO ()
main = putStrLn fizzbuzz100
fizzbuzz100 :: String
fizzbuzz100 = unwords $ fizzbuzz <$> [1..100]
fizzbuzz :: Int -> String
fizzbuzz n
| n `mod` 15 == 0 = "FizzBuzz"
| n `mod` 5 == 0 = "Buzz"
| n `mod` 3 == 0 = "Fizz"
| otherwise = show n
To run it, save it in fizzbuzz.hs and run it:
lira@computer:~/Projects/fizzbuzz$ chmod +x ./fizzbuzz.hs lira@computer:~/Projects/fizzbuzz$ ./fizzbuzz.hs 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz 31 32 Fizz 34 Buzz Fizz 37 38 Fizz Buzz 41 Fizz 43 44 FizzBuzz 46 47 Fizz 49 Buzz Fizz 52 53 Fizz Buzz 56 Fizz 58 59 FizzBuzz 61 62 Fizz 64 Buzz Fizz 67 68 Fizz Buzz 71 Fizz 73 74 FizzBuzz 76 77 Fizz 79 Buzz Fizz 82 83 Fizz Buzz 86 Fizz 88 89 FizzBuzz 91 92 Fizz 94 Buzz Fizz 97 98 Fizz Buzz
Quick sidenote: I am only using String here to keep the code short. From now on we will use the proper Data.Text!
3 Showing the result in a pop-up
3.1 Setting up the project with Qt6
Our user interface will be build with Qt6. As a first step, lets show a pop-up that contains the same text as was printed to the console.
The Qt6-API is written in C++, which is also the language we will use to connect to it. This means we will have three files: a Haskell module (Main.hs), some C++-glue (ui.cpp) and the project cabal file. Lets set it up!
Keep in mind that you need cabal 3.8+ because earlier versions were so hardwired to Hackage that this package also named fizzbuzz would cause conflicts...
lira@computer:~/Projects$ mkdir fizzbuzz && cd fizzbuzz lira@computer:~/Projects/fizzbuzz$ cabal init --simple --minimal --exe [Log] Using cabal specification: 3.0 [Warning] unknown license type, you must put a copy in LICENSE yourself. [Log] Creating fresh file CHANGELOG.md... [Log] Creating fresh directory ./app... [Log] Creating fresh file app/Main.hs... [Log] Creating fresh file fizzbuzz.cabal... [Warning] No synopsis given. You should edit the .cabal file and add one. [Info] You may want to edit the .cabal file and add a Description field. lira@computer:~/Projects/fizzbuzz$ mkdir cbits && touch cbits/ui.cpp lira@computer:~/Projects/fizzbuzz$ ls -lR {**,.}/*.{hs,cpp,cabal} 2>/dev/null -rw-r--r-- 1 Lira Lira 67 Jan 01 01:01 app/Main.hs -rw-r--r-- 1 Lira Lira 0 Jan 01 01:01 cbits/ui.cpp -rw-r--r-- 1 Lira Lira 369 Jan 01 01:01 ./fizzbuzz.cabal lira@computer:~/Projects/fizzbuzz$ cabal run Hello, Haskell!
In order for Haskell to call code from C++ we need to add some things to the cabal file:
cabal-version: 3.0
name: fizzbuzz
version: 1
executable fizzbuzz
main-is: Main.hs
build-depends: base, text
hs-source-dirs: app
default-language: Haskell2010
ghc-options: -Wall -Wno-tabs
pkgconfig-depends: Qt6Widgets
cxx-sources: cbits/ui.cpp
cxx-options: -std=c++23
extra-libraries: stdc++
- pkgconfig-depends
The command
pkg-configwill be used to find the needed linker flags andcflagsso we do not need to write these out ourselves manually. Runpkg-config --cflags Qt6Widgetsandpkg-config --libs Qt6Widgetsto view the retrieved flags.lira@computer:~/Projects/fizzbuzz$ pkg-config --cflags Qt6Widgets -I/usr/include/qt6/QtWidgets -I/usr/include/qt6 -DQT_WIDGETS_LIB -I/usr/lib/qt6/mkspecs/linux-g++ -I/usr/include/qt6/QtGui -DQT_GUI_LIB -I/usr/include/qt6/QtCore -DQT_CORE_LIB lira@computer:~/Projects/fizzbuzz$ pkg-config --libs Qt6Widgets -lQt6Widgets -lQt6Gui -lQt6Core
Output of pkg-config - cxx-sources
- The
.cpp-files we want to include in our build. - cxx-options
- Extra options we want to give to the compiler. With
-stdwe can specify the C++-version. We use the latest version, C++23, but a lower version like C++17 also works. - extra-libraries
- Here we can specify the standard library,
stdc++.
3.2 Name mangling
The functions we will call need to be defined on the C++-side and need to be callable from the Haskell-side. This means that on both sides the name should be the same.
The problem is, C++ supports function overloading, and solves the issue with uniqueness by changing the name behind the scenes based on the parameters. This means there is no way to know what the function will be called in the end after the name mangling when it gets compiled.
If we have two functions void test(int foo) and void test(int foo, int bar), they will have their name changed (mangled) to something like void voidTestint(int foo) and void voidTestintint(int foo, int bar) so they are unique again. What that name is depends on the compiler.
We can solve this by enclosing the functions whose names we don't want to get mangled in an extern "C"-block. This will disable features like function overloading and forces us to only use C-only types in the signature.
The extern "C" block can be used on only one function as well, as shown by test3 in the following example.
extern "C" {
void test1(int foo){
//...
}
void test2(int foo){
//...
}
}
extern "C" void test3(int foo){
//...
}
extern "C"-block enclosing two functions test1 and test2 between curly brackets and an extern "C"-block containing the function test3 Another way to prevent name mangling in GCC is with the asm-declaration.
void test(int foo) asm("test");
void test(int foo){
//...
}
asm-declaration3.3 Writing the pop-up code
To create a pop-up we need to start Qt6 by making a QApplication. When the application is started, we can show the pop-up with QMessageBox' exec.
#include<QApplication>
#include<QMessageBox>
extern "C" {
void showFizzbuzz(const char* message){
int x = 1;
QApplication app(x, new char*[1] { (char*)"Fizzbuzz" });
QMessageBox msgBox;
msgBox.setText(message);
msgBox.exec();
app.quit();
}
}
After the pop-up is closed, we call app.quit and the application closes.
3.4 Datatypes
Haskell has a nice type system. C does not. When we call functions via FFI we cannot use types like Text or Integer.
The Foreign.*-modules have the types that map to C we need for FFI:
| Haskell | Haskells C-types | C |
|---|---|---|
t :: Char |
t :: Foreign.C.Types.CChar |
char t |
t :: Int |
t :: Foreign.C.Types.CInt |
int t |
t :: Text |
t :: Foreign.C.String.CString (alias of Ptr CChar) |
char* t |
t :: a |
t :: Foreign.Ptr.Ptr a |
a* t |
t :: IO () |
t :: IO () |
void t |
t :: IO a (as a value) |
t :: IO a |
a t |
t :: IO a (as an action) |
t :: Foreign.Ptr.FunPtr (IO a) |
a (* t)() |
t :: a -> IO b |
t :: Foreign.Ptr.FunPtr (a -> IO b) |
b (* t)(a) |
3.5 Foreign imports
The syntax for importing foreign functions, like showFizzbuzz, is:
foreign import convention "function_name" haskell_function_name :: TYPE
There are two main conventions used in Haskell: ccall and capi. Both are used to call c-functions, or functions exported via a c-interface.
A lot of tutorials will tell you to always use capi, but as we will see later that is not always possible. For now I will only use the convention ccall since that almost always works and is easier to explain. Later we will cover capi and see when one is better than the other depending on the situation.
3.6 Foreign imports with IO
All C-functions have side-effects, and as such have a return type of IO a.
Let's say that we have the function int add5(int foo). We import it as add5 :: CInt -> IO CInt.
--Safe!
foreign import ccall "add5" add5 :: CInt -> IO CInt
add5 safelyWe can also import it with a return type a. Doing it this way is essentially the same as what we did before, but with unsafePerformIO called on the result.
--Not safe!
foreign import ccall "add5" add5 :: CInt -> CInt
add5 unsafelyThe second option should only be used when you know for certain the function does not have side-effects. In this article we always import with IO.
3.7 Opening the pop-up from Haskell
3.7.1 Importing a c++-function
In order to use void showFizzbuzz(const char* message), we have to import it. void matches with IO (), const char* matches with CString.
foreign import ccall "showFizzbuzz" c_showFizzbuzz :: CString -> IO ()
showFizzbuzzYou might notice the prefix c_. This is done so we can still create a function showFizzbuzz that we will use in our application, and has parameters with non-C-datatypes. We don't want to work with CString in the rest of our application!
3.7.2 withCString
Freeing memory is not something we have to deal with normally in Haskell. With the FFI we do need to think about it. Or better yet, we have to make Haskell think about it.
There are functions like newCString that allow you to create CStrings but they also require you to keep track of the pointers yourself.
This is where withCString :: Text -> (CString -> IO ()) -> IO () comes in handy! You give it a Text, a function that needs the CString and it will run it for you and also free up the memory afterwards, even when exceptions are thrown.
With withCString we can write the showFizzbuzz-function we want to use in the rest of our application:
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Prelude hiding (unwords)
import Data.Text (Text, pack, unwords)
import Data.Text.Foreign (withCString)
import Foreign.C.String (CString)
main :: IO ()
main = showFizzbuzz fizzbuzz100
foreign import ccall "showFizzbuzz" c_showFizzbuzz :: CString -> IO ()
showFizzbuzz :: Text -> IO ()
showFizzbuzz t = withCString t c_showFizzbuzz
fizzbuzz100 :: Text
fizzbuzz100 = unwords $ fizzbuzz <$> [1..100]
fizzbuzz :: Int -> Text
fizzbuzz n
| n `mod` 15 == 0 = "Fizzbuzz"
| n `mod` 5 == 0 = "Buzz"
| n `mod` 3 == 0 = "Fizz"
| otherwise = pack $ show n
3.8 Screenshot
4 Imports used from now on
Keeping track of which imports to add or remove can be a bit annoying.
For the rest of this article, the following build-depends will be needed for us to use the continuation-, state-, reader- and unliftIO-monad; and for deriving Storable automatically:
executable fizzbuzz
--...
build-depends:
base, text, mtl, derive-storable, unliftio-core
--...
We need the following Qt6-imports:
#include<QtWidgets>
We need the following imports in Haskell
{-# LANGUAGE OverloadedStrings, FlexibleContexts, OverloadedRecordDot, NamedFieldPuns, DeriveGeneric, CApiFFI #-}
module Main where
import Control.Exception (bracket, finally)
import Control.Monad.Cont (cont, runCont)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.IO.Unlift (MonadUnliftIO, askRunInIO)
import Control.Monad.Reader (MonadReader, runReaderT, ask)
import Data.IORef (IORef, newIORef, readIORef, modifyIORef')
import Data.Text (Text, pack)
import Data.Text.Foreign (withCString)
import Foreign.C.Types (CInt(CInt))
import Foreign.C.String (CString)
import Foreign.Marshal.Alloc (malloc, free)
import Foreign.Marshal.Array (withArrayLen)
import Foreign.Ptr (Ptr, FunPtr, freeHaskellFunPtr)
import Foreign.Storable (Storable(poke))
import Foreign.Storable.Generic (GStorable)
import GHC.Generics (Generic)
5 Showing the results as separate labels in a window
5.1 Modifying the pop-up code
Passing an entire string as the result is one option, but a better one might be to pass all the different results and print them individually. In order to do that, we first need to change the signature of showFuzzbuzz:
void showFizzbuzz(int numbersLength, const char** numbers){
showFizzbuzz in C++We now accept both a list of char*'s and its length, the latter we need in order to know how much we have to iterate over the numbers.
In showFizzbuzz we now also have to change the code from showing a pop-up to showing a grid with labels:
auto window = new QWidget();
auto box = new QGridLayout(window);
for(auto i = 0; i < numbersLength; i++){
auto label = new QLabel();
label->setText(numbers[i]);
box->addWidget(label, floor(i / 10), i % 10);
}
window->show();
app.exec();
5.2 Modifying the imports to showFuzzbuzz
Now we need to update the imports to match the signature on C++'s side.
foreign import ccall "showFizzbuzz" c_showFizzbuzz :: CInt -> Ptr CString -> IO ()
showFizzbuzz :: [Text] -> IO ()
showFizzbuzz t = withCStringArray t c_showFizzbuzz
There is only one problem: there is no such thing as withCStringArray, we will have to write that ourselves!
5.3 Converting a list to an array with the continuation monad
Sending a string is nice and all, but it would have been nicer if we could send the 100 strings to the function directly, instead of having to unwords them into a single string.
There is one problem: withCString accepts a function that deals with the CString instead of giving it so we can use it ourselves. Converting multiple strings would look like this:
f = withCString a \a' -> do
withCString b \b' -> do
withCString c \c' -> do
c_foo a' b' c'
We cannot convert a list this way, since the length is variable.
This is were the continuation monad comes in!
In order to use the continuation monad, we need a way to convert [Text] to a Cont (IO a) [CString]:
convertListToCont :: [Text] -> Cont (IO ()) [CString]
convertListToCont t = traverse (cont . withCString) t
ContNext we need a way to execute the function that accepts the CStrings from the list. We can do that with withArrayLen, which sadly passes a Int as its first argument instead of a CInt, forcing us to convert it with fromIntegral.
execute ::
(CInt -> Ptr CString -> IO ()) ->
[CString] ->
IO ()
execute f xs = withArrayLen' xs f
withArrayLen' :: Storable a => [a] -> (CInt -> Ptr a -> IO ()) -> IO ()
withArrayLen' xs f = withArrayLen xs $ \l xs' -> f (fromIntegral l) xs'
The last step is glueing those two together with <$> and running the continuation with `runCont` id:
withCStringArray ::
[Text] ->
(CInt -> Ptr CString -> IO ()) ->
IO ()
withCStringArray t f = ((`withArrayLen'` f) <$> traverse (cont . withCString) t) `runCont` id
withCStringArrayLater on we will also need to send other things to the C-side, so we can easily change the code to work on all types of Storable, not just CString, by creating the generic withCXArray that accepts a function like withCString:
withCXArray :: (Storable b) =>
(a -> (b -> IO ()) -> IO ()) ->
[a] ->
(CInt -> Ptr b -> IO ()) ->
IO ()
withCXArray withCX t f = ((`withArrayLen'` f) <$> traverse (cont . withCX) t) `runCont` id
withCStringArray ::
(Text -> (CString -> IO ()) -> IO ()) -> [Text] -> (CInt -> Ptr CString -> IO ()) -> IO ()
withCStringArray = withCXArray withCString
withCXArrayNow if only Foreign.Marshal.Array had a function like this, that would be so handy! But sadly all functions there assume the list you give is already of type Storable a => [a]...
5.4 Updating main
Now main can be written as follows:
main :: IO ()
main = showFizzbuzz $ fizzbuzz <$> [1..100]
main5.5 Screenshot
6 Showing the results in colourful labels by using structs for metadata
Until now we have only dealt with basic types, but sometimes we want to pass more complex data via the FFI. We can use structs for this.
6.1 Creating the struct
We want to send the text and color, so those two should be stored together. We will also store the original number:
enum Color: int {
Red = 0,
Orange = 1,
Green = 2,
Blue = 3,
};
struct FizzBuzz {
int number;
char* label;
Color color;
};
data Color
= Red
| Orange
| Green
| Blue
deriving Enum
data FizzBuzz = FizzBuzz
{ number :: Int
, label :: Text
, color :: Color
}
data CFizzBuzz = CFizzBuzz
{ cNumber :: CInt
, cLabel :: CString
, cColor :: CInt
}
We will use FizzBuzz in our application, and CFizzBuzz for the FFI.
In order for a type to be passable via the FFI via Haskell, it needs to derive Storable. This means that CFizzBuzz needs to derive Storable
There are libraries that can help you derive Storable automatically, but to see how it works, let's write one by hand.
6.2 Writing a Storable-instance by hand
6.2.1 The anatomy of Storable
The definition of Storable boils down to the following code:
class Storable a where
{-# MINIMAL sizeOf, alignment,
(peek | peekElemOff | peekByteOff),
(poke | pokeElemOff | pokeByteOff) #-}
sizeOf :: a -> Int
alignment :: a -> Int
peekElemOff :: Ptr a -> Int -> IO a
pokeElemOff :: Ptr a -> Int -> a -> IO ()
peekByteOff :: Ptr b -> Int -> IO a
pokeByteOff :: Ptr b -> Int -> a -> IO ()
peek :: Ptr a -> IO a
poke :: Ptr a -> a -> IO ()
StorableWith peek we can get the value from a Ptr, with poke we can put a vaue into a pointer. sizeOf and alignment are needed for specifying where on memory the value can be stored.
We always need to implement sizeOf and alignment. For peeking and poking we can choose which of the three functions we implement: the general case, for an element or for a byte.
6.2.2 sizeOf
The size of a type is the total amount of memory in bytes it will need to be stored. For a char this will be 1, for 32-bit int this will be 4, for a pointer this will be 8 and so on.
6.2.3 alignment
The alignment of a type is at what offset it can be saved.
Instructions are written for specific alignments, and wrong alignments could result in things like multiple reads or values being written over memory page boundaries.
When this is not correctly set it could result in worse performance or even access violations.
6.2.4 The size of a struct
This is where things get tricky, because we cannot just add the size of all the fields together, we have to take the alignments of all these types into consideration as well!
We will have to take the position of the last field, and add its size to it:
offsetNumber, offsetLabel, offsetColor :: Int
alignmentNumber, alignmentLabel, alignmentColor :: Int
sizeOfNumber, sizeOfLabel, sizeOfColor :: Int
alignmentNumber = alignment (undefined :: CInt)
alignmentLabel = alignment (undefined :: CString)
alignmentnColor = alignment (undefined :: CInt)
sizeOfNumber = sizeOf (undefined :: CInt)
sizeOfLabel = sizeOf (undefined :: CString)
sizeOfnColor = sizeOf (undefined :: CInt)
offsetNumber = 0
offsetLabel = offset0 + ((sizeOfNumber `div` alignmentLabel) + 1) * alignmentLabel
offsetColor = offset1 + ((sizeOfLabel `div` alignmentColor) + 1) * alignmentColor
instance Storable CFizzBuzz where
{- ... -}
sizeOf _ = offset2 + sizeOfColor
{- ... -}
sizeOf of CFizzBuzzWe take advantage here of the fact that ⊥, undefined, is a value that exists on all types.
6.2.5 alignment of a struct
With the alignment here we only need to make sure that the alignment of the fields are always correct. As such, the alignment needs to be the highest alignment of the struct.
instance Storable CFizzBuzz where
{- ... -}
alignment _ = maximum
[ alignmentNumber
, alignmentLabel
, alignmentColor
]
{- ... -}
alignment of CFizzBuzz6.2.6 poking the struct
When we want to create a struct, we have to poke it into a pointer. When poking we have to poke the fields at the correct place.
We can reuse the offsets we also used for sizeOf.
instance Storable CFizzBuzz where
{- ... -}
poke p fb = do
pokeByteOff p offset0 fb.cNumber
pokeByteOff p offset1 fb.cLabel
pokeByteOff p offset2 fb.cColor
{- ... -}
pokeing to CFizzBuzz6.2.7 peeking the struct
With peek we can use the applicative style of IO!
instance Storable CFizzBuzz where
{- ... -}
peek p = CFizzBuzz
<$> peekByteOff p offset0
<*> peekByteOff p offset1
<*> peekByteOff p offset2
{- ... -}
peeking from CFizzBuzz6.2.8 Realizing a more efficient order of fields exist
We just finished the instance, and we just realized something! We can change the order of the fields so it is more efficient!
struct FizzBuzz {
int number;
char* label;
Color color;
};
With the order as it is now, there will be a gap of 4 bytes between number and label because of label's alignment. This means that the total size of FizzBuzz is 20.
If we move color together with number, color will fill that gap and the total size will be 16.
struct FizzBuzz {
int number;
Color color;
char* label;
};
We could change that, but it would mean changing the implementation at multiple places. Forgetting it in one place, or doing it wrong in one place, could result in our application not working correctly, or even crashing.
When writing structs, it is probably best to use one of the tools that automatically generate these instances.
6.3 Letting GStorable generate our Storable-instance
As we just saw, writing it ourselves is not fun, especially not when having to refactor the code. So lets try the package GStorable!
With GStorable we have to make CFizzBuzz derive Generic and GStorable. When we do that it will automatically have a Storable-instance.
{- ... -}
{-# LANGUAGE DeriveGeneric #-}
{- ... -}
import Foreign.Storable.Generic (GStorable)
import GHC.Generics (Generic)
{- ... -}
data CFizzBuzz = CFizzBuzz
{ cNumber :: CInt
, cColor :: CInt
, cLabel :: CString
} deriving (Generic)
instance GStorable CFizzBuzz
CFizzBuzz with GStorableEasy, isn't it?
We also fixed the order in CFizzBuzz! The order in Haskells FizzBuzz is not important and can remain the same.
6.4 Creating a CFizzBuzz
Now that we made CFizzBuzz a Storable we need a way to convert a FizzBuzz into it.
The fields have to be converted, but we also need to make sure that CFizzBuzz gets cleaned up correctly when it is no longer needed.
To create it, we need to poke a CFizzBuzz into a pointer. And we need to keep track of the lifetime of that pointer.
An easy way to do that is with bracket. Its first argument is for creating the pointer, the second for freeing it. After we specified those we get our pointer we can use.
Once we poked CFizzBuzz into the pointer we have to give it to pass it to the second argument of withCFizzBuzz.
{- ... -}
import Foreign.Marshal.Alloc (malloc, free)
{- ... -}
withCFizzBuzz :: FizzBuzz -> (Ptr CFizzBuzz -> IO ()) -> IO ()
withCFizzBuzz fb f = do
withCString fb.label $ \l -> do
bracket malloc free $ \ptr -> do
poke ptr CFizzBuzz
{ cNumber = fromIntegral fb.number
, cColor = fromIntegral $ fromEnum fb.color
, cLabel = l
}
f ptr
withCFizzBuzz6.5 Updating the labels
Now that we have to pass FizzBuzzes, we have to update our functions as well.
Let's start on the C++-side. We have to change the signature, and also how the text should be set.
After that we can add the styling, depending on the color of the item.
void showFizzBuzz(int itemsLength, FizzBuzz** items){
//...
label->setText(items[i]->label);
switch(items[i]->color){
case Color::Red:
label->setStyleSheet("QLabel { background-color: red }");
break;
case Color::Orange:
label->setStyleSheet("QLabel { background-color: orange }");
break;
case Color::Green:
label->setStyleSheet("QLabel { background-color: green }");
break;
case Color::Blue:
label->setStyleSheet("QLabel { background-color: blue }");
break;
}
box->addWidget(label, floor(i / 10), i % 10);
//...
showFizzBuzz6.6 Updating fizzBuzz
On the Haskell-side we first have to change the foreign import:
foreign import ccall "showFizzbuzz" c_showFizzbuzz :: CInt -> Ptr CFizzBuzz -> IO ()
showFizzbuzz :: [FizzBuzz] -> IO ()
showFizzbuzz t = withCXArray withCFuzzBuzz t c_showFizzbuzz
showFizzBuzzNext we have to change fizzBuzz to return FizzBuzzes instead of Texts:
fizzBuzz :: Int -> FizzBuzz
fizzBuzz n
| n `mod` 15 == 0 = FizzBuzz n "Fizzbuzz" Red
| n `mod` 5 == 0 = FizzBuzz n "Buzz" Orange
| n `mod` 3 == 0 = FizzBuzz n "Fizz" Green
| otherwise = FizzBuzz n (pack $ show n) Blue
fizzBuzz to return FizzBuzz6.7 Screenshot
7 Showing the results in colourful buttons that show a pop-up when clicked
7.1 Labels to buttons
While we have a nice looking interface now, it can be confusing what number is behind a Fizz
, Buzz
of FizzBuzz
! We can change the labels to buttons and show the number behind it when clicked.
Changing the labels to buttons is a simple change:
for(auto i = 0; i < numbersLength; i++){
auto btn = new QPushButton();
btn->setText(numbers[i]->label);
//...
box->addWidget(btn, floor(i / 10), i % 10);
}
QLabel to QPushButtonNow all labels are buttons, but there is still no interaction!
7.2 Hooking a callback to a button
In Qt6 you can hook a callback to a signal. The signal for clicking a QPushButton is QAbstractButton::released().
The type for the callback we want to hook onto it is void (*ButtonClickCallback)():
typedef void (*ButtonClickCallback)();
ButtonClickCallbackWhile all buttons will do the same thing, showing the underlying number, lets have FizzBuzz also include the callback to be called.
struct FizzBuzz {
int number;
char* label;
Color color;
ButtonClickCallback callback;
};
FizzBuzzNow we can hook this callback to the button
QObject::connect(btn, &QAbstractButton::released, items[i]->callback);
callback to the button7.3 Creating a callback
As we did in C++, we also create a type for the callback in Haskell and add it to FizzBuzz.
type ButtonCallback = IO ()
data FizzBuzz = FizzBuzz
{ number :: Int
, label :: Text
, color :: Color
, callback :: ButtonCallback
}
ButtonCallback7.4 Putting the Fun in FunPtr
We cannot add a field to CFizzBuzz like this. For CFizzBuzz we need a function pointer, a FunPtr.
data CFizzBuzz = CFizzBuzz
{ cNumber :: CInt
, cLabel :: CString
, cColor :: CInt
, cCallback :: FunPtr ButtonCallback
} deriving (Generic)
cCallback to CFizzBuzzCreating a FunPtr can be done with the function wrapper which we can import via the function interface. The function is some kind of compiler magic, it is not a real function.[2]
foreign import ccall "wrapper" mkButtonCallback :: ButtonCallback -> IO (FunPtr ButtonCallback)
ButtonCallback in a FunPtrWith this function we can now extend withCFizzBuzz to also add the callback.
withCFizzBuzz fb f = do
cCallback <- mkButtonCallback fb.callback
withCString fb.label $ \l -> do
bracket malloc free $ \ptr -> do
poke ptr CFizzBuzz
{ cNumber = fromIntegral fb.number
, cLabel = l
, cColor = fromIntegral $ fromEnum fb.color
, cCallback
}
f ptr
cCallback in withCFizzBuzz7.5 Cleaning up the FunPtrs
Like other pointers, FunPtrs also have to be freed. We can free the function pointers with freeHaskellFunPtr.
We have to clear the pointers when we no longer need the buttons, which is when we quit the application. We cannot do it immediately or we get undefined behaviour.
This means we need to store all the FunPtrs and free them at the end.
But how do we store it?
7.5.1 The naïve way: MonadState [FunPtr] m
Since we need to keep track of something, MonadState seems like a logical choice.
main = do
let
funPtrs = []
funPtrsAfterRun = execStateT (showFizzbuzz $ fizzbuzz <$> [1..100]) funPtrs
freeHaskellFunPtr `mapM_` funPtrsAfterRun
main to have stateThis will work. At least, it will look like it does. And it does free the function pointers! But not always. Not when things go wrong, like when an exception gets thrown somewhere.
It is not possible to catch here and clean up.
We need something that knows about the outside world, and can store values: IORef.
7.5.2 A better MonadState [FunPtr] m: MonadReader (IORef [FunPtr]) m
This means that we don't need MonadState, but we need an IORef that we can access, via a MonadReader
main = do
funPtrsRef <- newIORef []
runReaderT (showFizzbuzz $ fizzbuzz <$> [1..100]) funPtrsRef `finally` do
funPtrs <- readIORef funPtrsRef
freeHaskellFunPtr `mapM_` funPtrs
We have to change withFizzBuzz to add the newly created FunPtr to the IORef. This also means we have to change the type to that of a monad reader.
type FunPtrs = IORef [FunPtr (IO ())]
withCFizzBuzz :: (MonadReader FunPtrs m, MonadIO m)
=> FizzBuzz -> (Ptr CFizzBuzz -> IO ()) -> m ()
withCFizzBuzz fb f = do
funPtrs <- ask
liftIO $ do
cCallback <- mkButtonCallback fb.callback
withCString fb.label $ \l -> do
bracket malloc free $ \ptr -> do
poke ptr CFizzBuzz
{ cNumber = fromIntegral fb.number
, cLabel = l
, cColor = fromIntegral $ fromEnum fb.color
, cCallback
}
f ptr
modifyIORef' funPtrs (cCallback:)
FunPtrs to the listNext we modify showFizzBuzz to pass the IORef we created in main to withCFizzBuzz.
showFizzbuzz :: (MonadIO m, MonadReader FunPtrs m) => [FizzBuzz] -> m ()
showFizzbuzz t = withCXArray withCFizzBuzz t c_showFizzbuzz
showFizzBuzz to also use the reader monadUh-oh! This does not work because withCXArray expects a IO, not a (MonadIO m) => m. There is liftIO :: (MonadIO m) => m () -> IO () but we need to opposite, we need to unlift!
7.5.3 Accessing the monad stack within IO with MonadUnliftIO
With MonadUnliftIO we can get the monad stack into IO. askRunInIO gives us the helpler-function run :: m a -> IO a.
With run we can wrap withCFizzBuzz and execute it within withCXArray.
showFizzbuzz :: (MonadIO m, MonadReader FunPtrs m, MonadUnliftIO m) => [FizzBuzz] -> m ()
showFizzbuzz t = do
run <- askRunInIO
liftIO $ withCXArray (fb action -> run $ withCFizzBuzz fb action) t c_showFizzbuzz
showFizzBuzz a MonadUnliftIOAnother option would have been to take the state out of the monad reader, and run a new reader within withCFizzBuzz. But that would make the code less readable.
7.6 Screenshot
8 Using foreign export
Just like we can import things, we can also export things! To stay in the spirit of little applications like FizzBuzz, lets create a function that prints Hello world
and run it on the C++-side!
foreign export ccall printHelloWorld :: IO ()
printHelloWorld :: IO ()
printHelloWorld = putStrLn "Hello world"
printHelloWorldWith this foreign export Haskell will generate a .h- and .c-file for us, which we can import to get printHelloWorld! The name is the name of the module, followed by _stub.h
.
#include "Main_stub.h"
//...
void showFizzbuzz(int itemsLength, FizzBuzz** items){
int x = 1;
QApplication app(x, new char*[1] { (char*)"Fizzbuzz" });
printHelloWorld();
//...
}
printHelloWorldlira@computer:~/Projects/fizzbuzz$ cabal run Hello world
9 On CApiFFI
9.1 When to use the CApiFFI-extension
When you want to stick to the language standard, you should always use ccall since capi is a GHC-extension. This means that ccall will also work for other compilers.
When you need wrapper
or dynamic
to create function pointers or to convert function pointers to proper functions, you also need to use ccall since this is not supported in capi.
When you have a function with a variable number of arguments, you should always use capi since ccall doesn't support it.
That leaves us with the common situation: using GHC Haskell and external code via the C-ABI or C-API. When using the C-ABI, we can keep using ccall, when using the C-API we can use capi.
9.2 Changing the ccalls to capi
For foreign export the only thing we need to do is change ccall to capi
foreign export capi printHelloWorld :: IO ()
printHelloWorld :: IO ()
printHelloWorld to capiFor foreign import we cannot just change ccall to capi, we also need to specify a header file.
Copying the signature of the two functions showMessage and showFizzbuzz also means we have to move Color and FizzBuzz to the header. Keep in mind that we also need to typedef them, otherwise capi will not be able to find the types as it expects struct FizzBuzz instead of FizzBuzz!
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
typedef void (*ButtonClickCallback)();
typedef enum Color: int {
Red = 0,
Orange = 1,
Green = 2,
Blue = 3,
} Color;
typedef struct FizzBuzz {
int number;
char* label;
Color color;
ButtonClickCallback callback;
} FizzBuzz;
void showMessage(const char* message);
void showFizzbuzz(int itemsLength, FizzBuzz** items);
#ifdef __cplusplus
}
#endif
As you can see we start the file with #pragma once. This is because the generated code for capi includes this header many times, for each foreign import. You can view this if you run cabal run --ghc-options=-keep-tmp-files and go to the /tmp-folder that gets mentioned in the output — normally something like /tmp/ghcxxxx_0/ghc_2.c.
We also use #ifdef _cplusplus-pragmas around extern "C", because sometimes this header gets read for C (ghc_2.c) and sometimes for C++ (cbits/ui.cpp) and it needs to work for both. Of course, since there is a #pragma once either C or C++ will win so leaving it out will only work sometimes.
With our new header-file fizzbuzz.cabal should now include the following:
extra-source-files: cbits/*.cpp, cbits/*.h
executable fizzbuzz
--...
pkgconfig-depends: Qt6Widgets
cxx-sources: cbits/ui.cpp
cxx-options: -std=c++17
includes: ui.h
include-dirs: ./cbits
extra-libraries: stdc++
Now that we did that... it still doesn't work! capi seems to generate code that maps Ptr CFizzBuzz to void* instead of FizzBuzz*.
error: passing argument 2 of ‘showFizzbuzz’ from incompatible pointer type [-Wincompatible-pointer-types]
|
31 | void ghczuwrapperZC2ZCmainZCMainZCshowFizzzzbuzzzz(HsInt32 a1, void** a2) {showFizzbuzz(a1, a2);}
| ^
In file included from /tmp/ghc19182_0/ghc_2.c:28:0: error:
cbits/ui.h:21:62: error:
note: expected ‘struct FizzBuzz **’ but argument is of type ‘void **’
|
21 | void showFizzbuzz(int itemsLength, struct FizzBuzz** items);
| ^
This is because we haven't told GHC what CFizzBuzz maps to! For this we need the CType-pragma.
data {-# CTYPE "ui.h" "FizzBuzz" #-} CFizzBuzz = CFizzBuzz
{ cNumber :: CInt
, cLabel :: CString
, cColor :: CInt
, cCallback :: FunPtr ButtonCallback
} deriving (Generic)
CFizzBuzzNow it works!
As you can see, capi is a lot of extra work but it does give you extra safety! It is best to always use it external libraries outside of your control. For your own code you can choose what fits you better, ccall or capi
10 Final code
{-# LANGUAGE OverloadedStrings, FlexibleContexts, OverloadedRecordDot, NamedFieldPuns, DeriveGeneric, CApiFFI #-}
module Main where
import Control.Exception (bracket, finally)
import Control.Monad.Cont (cont, runCont)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.IO.Unlift (MonadUnliftIO, askRunInIO)
import Control.Monad.Reader (MonadReader, runReaderT, ask)
import Data.IORef (IORef, newIORef, readIORef, modifyIORef')
import Data.Text (Text, pack)
import Data.Text.Foreign (withCString)
import Foreign.C.Types (CInt(CInt))
import Foreign.C.String (CString)
import Foreign.Marshal.Alloc (malloc, free)
import Foreign.Marshal.Array (withArrayLen)
import Foreign.Ptr (Ptr, FunPtr, freeHaskellFunPtr)
import Foreign.Storable (Storable(poke))
import Foreign.Storable.Generic (GStorable)
import GHC.Generics (Generic)
main :: IO ()
main = do
funPtrsRef <- newIORef []
runReaderT (showFizzbuzz $ fizzbuzz <$> [1..100]) funPtrsRef `finally` do
funPtrs <- readIORef funPtrsRef
freeHaskellFunPtr `mapM_` funPtrs
data Color
= Red
| Orange
| Green
| Blue
deriving Enum
type ButtonCallback = IO ()
data FizzBuzz = FizzBuzz
{ number :: Int
, label :: Text
, color :: Color
, callback :: ButtonCallback
}
fizzbuzz :: Int -> FizzBuzz
fizzbuzz n
| n `mod` 15 == 0 = FizzBuzz n "Fizzbuzz" Red click
| n `mod` 5 == 0 = FizzBuzz n "Buzz" Orange click
| n `mod` 3 == 0 = FizzBuzz n "Fizz" Green click
| otherwise = FizzBuzz n (pack $ show n) Blue click
where
click :: IO ()
click = showMessage (pack $ show n)
foreign export capi printHelloWorld :: IO ()
printHelloWorld :: IO ()
printHelloWorld = putStrLn "Hello world"
type FunPtrs = IORef [FunPtr (IO ())]
data {-# CTYPE "ui.h" "FizzBuzz" #-} CFizzBuzz = CFizzBuzz
{ cNumber :: CInt
, cLabel :: CString
, cColor :: CInt
, cCallback :: FunPtr ButtonCallback
} deriving (Generic)
instance GStorable CFizzBuzz
foreign import capi "ui.h showFizzbuzz" c_showFizzbuzz :: CInt -> Ptr (Ptr CFizzBuzz) -> IO ()
showFizzbuzz :: (MonadIO m, MonadReader FunPtrs m, MonadUnliftIO m) => [FizzBuzz] -> m ()
showFizzbuzz t = do
run <- askRunInIO
liftIO $ withCXArray (\fb action -> run $ withCFizzBuzz fb action) t c_showFizzbuzz
foreign import capi "ui.h showMessage" c_showMessage :: CString -> IO ()
showMessage :: Text -> IO ()
showMessage t = withCString t c_showMessage
foreign import ccall "wrapper" mkButtonCallback :: ButtonCallback -> IO (FunPtr ButtonCallback)
withCXArray :: (Storable b)
=> (a -> (b -> IO ()) -> IO ()) -> [a] -> (CInt -> Ptr b -> IO ()) -> IO ()
withCXArray withCX t f = ((`withArrayLen'` f) <$> traverse (cont . withCX) t) `runCont` id
withArrayLen' :: Storable a => [a] -> (CInt -> Ptr a -> IO ()) -> IO ()
withArrayLen' xs f = withArrayLen xs $ \l xs' -> f (fromIntegral l) xs'
withCFizzBuzz :: (MonadReader FunPtrs m, MonadIO m) => FizzBuzz -> (Ptr CFizzBuzz -> IO ()) -> m ()
withCFizzBuzz fb f = do
funPtrs <- ask
liftIO $ do
cCallback <- mkButtonCallback fb.callback
withCString fb.label $ \l -> do
bracket malloc free $ \ptr -> do
poke ptr CFizzBuzz
{ cNumber = fromIntegral fb.number
, cLabel = l
, cColor = fromIntegral $ fromEnum fb.color
, cCallback
}
f ptr
modifyIORef' funPtrs (cCallback:)
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
typedef void (*ButtonClickCallback)();
typedef enum Color: int {
Red = 0,
Orange = 1,
Green = 2,
Blue = 3,
} Color;
typedef struct FizzBuzz {
int number;
char* label;
Color color;
ButtonClickCallback callback;
} FizzBuzz;
void showMessage(const char* message);
void showFizzbuzz(int itemsLength, FizzBuzz** items);
#ifdef __cplusplus
}
#endif
#include<QApplication>
#include<QGridLayout>
#include<QPushButton>
#include<QMessageBox>
#include "ui.h"
#include "Main_stub.h"
static const char* getStyleFromColor(Color color){
switch(color){
case Color::Red:
return "QPushButton { background-color: red }";
case Color::Orange:
return "QPushButton { background-color: orange }";
case Color::Green:
return "QPushButton { background-color: green }";
case Color::Blue:
return "QPushButton { background-color: blue }";
}
return "";
}
extern "C" {
void showMessage(const char* message){
QMessageBox msgBox;
msgBox.setText(message);
msgBox.exec();
}
void showFizzbuzz(int itemsLength, FizzBuzz** items){
int x = 1;
QApplication app(x, new char*[1] { (char*)"Fizzbuzz" });
printHelloWorld();
auto window = new QWidget();
auto box = new QGridLayout(window);
for(auto i = 0; i < itemsLength; i++){
auto btn = new QPushButton();
btn->setText(items[i]->label);
btn->setStyleSheet(getStyleFromColor(items[i]->color));
QObject::connect(btn, &QAbstractButton::released, items[i]->callback);
box->addWidget(btn, floor(i / 10), i % 10);
}
window->show();
app.exec();
}
}
cabal-version: 2.4
name: fizzbuzz
version: 1
executable fizzbuzz
main-is: Main.hs
build-depends:
base, text, mtl, derive-storable, unliftio-core
hs-source-dirs: app
default-language: Haskell2010
ghc-options: -Wall -Wno-tabs
pkgconfig-depends: Qt6Widgets
cxx-sources: cbits/ui.cpp
cxx-options: -std=c++17
includes: ui.h
include-dirs: ./cbits
extra-libraries: stdc++
11 Sources
- Name mangling, by Wikipedia
- Language linkage, by cppreference
- Best practices for foreign imports, by Ben Gamari
- Explanation about foreign import "wrapper" with ccall, by Ben Gamari
- GHC Commentary: Runtime aspects of the FFI, by GHC
- Implementation for the "wrapper" wrapper in Haskell FFI, answered by HTNW
- 6.17. Foreign function interface (FFI), by GHC Team