cgen

cgen generates Haskell bindings to C++ libraries

Animals - a simple example for cgen

Here's the input, a library with animals.

/* cpp/Animal.h */
#ifndef ANIMALS_ANIMAL_H
#define ANIMALS_ANIMAL_H

#include <iostream>

namespace Animals {
	class Animal {
		public:
			Animal()
				: age(0) { }
			~Animal() { }
			virtual void make_sound() = 0;
			int get_age() const
			{
				return age;
			}
			void increment_age()
			{
				age++;
			}
		private:
			int age;
	};
}

#endif
/* cpp/Dog.h */
#ifndef ANIMALS_DOG_H
#define ANIMALS_DOG_H

#include "Animal.h"

namespace Animals {
	class Dog : public Animal {
		public:
			Dog() { }
			void make_sound()
			{
				std::cout << "Growl!\n";
			}
	};
}

#endif
/* cpp/Sheep.h */
#ifndef ANIMALS_SHEEP_H
#define ANIMALS_SHEEP_H

#include "Animal.h"

namespace Animals {
	class Sheep : public Animal {
		public:
			Sheep(int wooliness_level_ = 0)
				: wooliness_level(wooliness_level_)
			{ }
			void make_sound()
			{
				std::cout << "Baa!\n";
			}
			void shear()
			{
				/* something */
			}
		private:
			int wooliness_level;
	};
}

#endif

C wrapper generation and compilation

The first thing to do is to create the C++ library with a C interface:

$ cgen -o c --header=Animal.h --header=Dog.h \
    --header=Sheep.h cpp/Dog.h cpp/Sheep.h cpp/Animal.h

This command creates the C wrapper in directory "c". The given header files are those that the files in the C wrapper need to include in order to compile. This is what the output looks like:

$ cat c/Sheep.h
fndef CGEN_SHEEP_H
#define CGEN_SHEEP_H

#include <Sheep.h>
#include <Dog.h>
#include <Animal.h>

extern "C"
{

using namespace Animals;



#ifdef CGEN_HS
#endif

void Animals_Sheep_delete(Sheep* this_ptr);
Sheep* Animals_Sheep_new(int wooliness_level_);
void Animals_Sheep_make_sound(Sheep* this_ptr);
void Animals_Sheep_shear(Sheep* this_ptr);

}

#endif
			
$ cat c/Sheep.cpp
#define CGEN_OUTPUT_INTERN
#include "Sheep.h"
void Animals_Sheep_delete(Sheep* this_ptr)
{
    delete this_ptr;
}

Sheep* Animals_Sheep_new(int wooliness_level_)
{
    return new Sheep(wooliness_level_);
}

void Animals_Sheep_make_sound(Sheep* this_ptr)
{
    this_ptr->make_sound();
}

void Animals_Sheep_shear(Sheep* this_ptr)
{
    this_ptr->shear();
}
			

Now, the C wrapper can be compiled and an archive file can be created for static linking:

$ g++ -c -Icpp -o c/Animal.o c/Animal.cpp
$ g++ -c -Icpp -o c/Dog.o c/Dog.cpp
$ g++ -c -Icpp -o c/Sheep.o c/Sheep.cpp
$ ar q libanimals.a c/Animal.o c/Dog.o c/Sheep.o

Haskell wrapper generation and compilation

For creating the Haskell bindings, a class inheritance graph must first be created using grgen. This is necessary for creating the functions for upcasting in Haskell, which is implicit in C++.

$ grgen -o graph cpp/Animal.h cpp/Dog.h cpp/Sheep.h
$ cat graph
Animal|
Dog|Animal
Sheep|Animal

Then, the Haskell wrapper can be generated using cgen-hs. The files will be written in the "hs" directory.

$ cgen-hs -o hs --inherit=graph c/Animal.h c/Sheep.h c/Dog.h

This is what the output looks like:

$ cat hs/Sheep.hs
{-# LANGUAGE ForeignFunctionInterface #-}
module Sheep(
sheep_with, 
sheep_delete, 
sheep_new, 
sheep_make_sound, 
sheep_shear
)

where

import Types
import Control.Monad

import Foreign
import Foreign.C.String
import Foreign.C.Types

sheep_with :: Int -> (Sheep -> IO a) -> IO a
sheep_with p1 f = do
    obj <- sheep_new p1
    res <- f obj
    sheep_delete obj
    return res

foreign import ccall "Sheep.h Animals_Sheep_delete" c_sheep_delete :: Sheep -> IO ()
sheep_delete :: Sheep -> IO ()
sheep_delete p1 =   c_sheep_delete p1

foreign import ccall "Sheep.h Animals_Sheep_new" c_sheep_new :: CInt -> IO Sheep
sheep_new :: Int -> IO Sheep
sheep_new p1 =   c_sheep_new (fromIntegral p1)

foreign import ccall "Sheep.h Animals_Sheep_make_sound"
        c_sheep_make_sound :: Sheep -> IO ()
sheep_make_sound :: Sheep -> IO ()
sheep_make_sound p1 =   c_sheep_make_sound p1

foreign import ccall "Sheep.h Animals_Sheep_shear" c_sheep_shear :: Sheep -> IO ()
sheep_shear :: Sheep -> IO ()
sheep_shear p1 =   c_sheep_shear p1
			

In addition to generating Haskell files that directly correspond to the header files, a file Types.hs is created, which has the code defining the upcasts mentioned above:

$ cat hs/Types.hs
module Types
where

import Foreign
import Foreign.C.String
import Foreign.C.Types

type CBool = CChar

newtype Animal = Animal (Ptr Animal) -- nullary data type
newtype Dog = Dog (Ptr Dog) -- nullary data type
newtype Sheep = Sheep (Ptr Sheep) -- nullary data type
newtype Void = Void (Ptr Void) -- nullary data type

class CAnimal a where
  toAnimal :: a -> Animal

instance CAnimal Dog where
  toAnimal (Dog p) = Animal (castPtr p)

instance CAnimal Sheep where
  toAnimal (Sheep p) = Animal (castPtr p)
			

Usage of the Haskell wrapper

Then, the Haskell library can be used:

$ cat > hs/Main.hs
module Main
where

import Control.Monad (replicateM_)

import Types
import Animal
import Dog
import Sheep

main = do
  dog_with $ \d -> 
    sheep_with 1 $ \s -> do
      dog_make_sound d
      sheep_make_sound s
      sheep_shear s
      replicateM_ 5 $ animal_increment_age (toAnimal s)
      animal_get_age (toAnimal s) >>= \a -> putStrLn $ "The sheep is now " ++
          show a ++ " years old!"

This can be compiled as follows (note linking against the created static library as well as the C++ runtime):

$ cd hs
$ ghc --make -o Main -L.. -lanimals -lstdc++ Main.hs

Result:

$ ./Main
Growl!
Baa!
The sheep is now 5 years old!

Try it out

Instead of copy-pasting you can clone the example with the following command:
git clone -b example-animals git://github.com/anttisalonen/cgen.git