5
votes

General Information

I'm currently experimenting with the C->Haskell (C2HS) Interface Generator for Haskell. At the first glace, it was just awesome, I interfaced a rather complicated C++ library (using a small extern C-wrapper) in just a couple of hours. (And I never did any FFI before.)

There was just one problem: How to get the memory allocated in the C/C++ library freed? I found {#pointer ... foreign #} in the C2HS documentation and that looks exactly like what I'm after. Since my C-wrapper turns the C++ library into a library with referential transparency with a functional interface, the Haskell Storage Manager should be able to do the hard work for me :-). Unfortunately, I was not able to get this working. To better explain my problem, I set up a small demo project on GitHub that has the same properties as the C/C++ library+wrapper but hasn't the overhead. As you can see, the library is completely safe to use with pure unsafe FFI.

Demo Project

On GitHub, I created a small demo project organized as follows:

C library

The C library is very simple and useless: You can pass it an integer and you can get as many integers (currently [0..n]) back from the library. Remember: The library is useless, just a demo. The interface is quite simple, too: The function LTIData lti_new_data(int n) will (after passing a integer) return you some kind of opaque object containing allocated data of the C library. The library has also two accessor functions int lti_element_count(LTIData data) and int lti_get_element(LTIData data, int n), the former will return the number of elements and the latter will return you element n. Ah, and last but not least, the user of the library should after using it free the opaque LTIData using void lti_free_data(LTIData data).

Low-level Haskell Binding

The low-level Haskell Binding is set up using C2HS, you can find it in

High-level Haskell API

For fun I also set up kind of a high-level Haskell API using the low-level API binding and a simple driver program that uses the high-level API. Using the driver program and e.g. valgrind one is easily able to see the leaked memory (for every parameter p_1, p_2, ..., p_n the library does \sum_{i = 1..n} 1 + p_i allocations; easily observable as below):

$ valgrind dist/build/TestHsLTI/TestHsLTI 100             2>&1 | grep -e allocs -e frees
==22647==   total heap usage: 184 allocs, 74 frees, 148,119 bytes allocated

$ valgrind dist/build/TestHsLTI/TestHsLTI 100 100         2>&1 | grep -e allocs -e frees
==22651==   total heap usage: 292 allocs, 80 frees, 181,799 bytes allocated

$ valgrind dist/build/TestHsLTI/TestHsLTI 100 100 100     2>&1 | grep -e allocs -e frees
==22655==   total heap usage: 400 allocs, 86 frees, 215,479 bytes allocated

$ valgrind dist/build/TestHsLTI/TestHsLTI 100 100 100 100 2>&1 | grep -e allocs -e frees
==22659==   total heap usage: 508 allocs, 92 frees, 249,159 bytes allocated

Current State of the Demo

You should be able to clone, compile and run the project by simply typing git clone https://github.com/weissi/c2hs-experiments.git && cd c2hs-experiments && cabal configure && cabal build && dist/build/TestHsLTI/TestHsLTI

So what's the Problem again?

The problem is that the project only uses Foreign.Ptr and not the "managed" version Foreign.ForeignPtr using C2HS's {#pointer ... foreign #} and I can't get it to work. In the demo project I also added a .chs file trying to use these foreign pointers but it does not work :-(. I tried it very hard but I didn't have any success.

And there is one thing I don't understand, too: How to tell GHC using C2HS how to free the library's data. The demo project's library provides a function void lti_free_data(LTIData data) that should get called to free the memory. But GHC can't guess that!?! If GHC uses regular a free(), not all of the memory will get freed :-(.

1

1 Answers

3
votes

Problem solved: I found this file doing something similar on the internet and was able to solve it :-).

Everything it needed was some boilerplate marshalling code:

foreign import ccall "lib_to_interface.h &lti_free_data"
  ltiFreeDataPtr :: FunPtr (Ptr (LTIDataHs) -> IO ())


newObjectHandle :: Ptr LTIDataHs -> IO LTIDataHs
newObjectHandle p = do
  fp <- newForeignPtr ltiFreeDataPtr p
  return $ LTIDataHs fp

Here's the final managed (ForeignPtr) verion of the .chs file.