Search code examples
c++haskellgarbage-collectionffi

Calling a function at object deletion in Haskell


I'm writing a Haskell wrapper for a C++ class. I decided to represent it as a Haskell Data structure containing a pointer (Foreign.Ptr) to the class instance in C++. Something like that.

In C++:

class MyClass {
public:
    double my_method();

    // ...
};

extern "C" MyClass* cpp_new_MyClass() {
    return new MyClass();
}

extern "C" double cpp_my_method(MyClass *obj) {
    return obj->my_method();
}

In Haskell:

Data MyClass = MyClass (Ptr ())

foreign import ccall "cpp_new_MyClass" cppNewMyClass :: Ptr () 
foreign import ccall "cpp_my_method" cppMyMethod :: Ptr () -> Double

mkMyClass :: MyClass
mkMyClass = MyClass cppNewMyClass

myMethod :: MyClass -> Double
myMethod (MyClass ptr) = cppMyMethod ptr

The problem is, I don't know how to correctly implement MyClass deletion. At some point, Haskell garbage collector will delete MyClass object, but it won't trigger MyClass* memory freeing in C++. How do I fix that?

I'm aware of ForeignPtr, but it uses IO monad, which is not satisfying because I want the wrapped data structure to behave exactly as a normal Haskell data structure, without the need for explicit allocating/freeing memory or IO monads.


Solution

  • “it uses IO monad, which is not satisfying because I want the wrapped data structure to behave exactly as a normal Haskell data structure”

    Sure you do, but unfortunately it's not really possible. Foreign “functions” can always do funny stuff that shouldn't be possible in Haskell; the type system has no way to look there and prevent it.

    That dilemma is the only (!) reason we have unsafePerformIO, and indeed yours is a good example of a valid application for that thing.

    I haven't done this myself yet, but your code should look something like the following:

    extern "C" void cpp_delete_MyClass(MyClass* obj) {
        delete obj;
    }
    
    foreign import ccall "cpp_new_MyClass" cppNewMyClass :: IO (Ptr ())
    foreign import ccall "&cpp_delete_MyClass" cppDeleteMyClass :: FunPtr (Ptr () -> IO ())
    
    data MyClass = MyClass (ForeignPtr ())
    
    mkMyClass :: MyClass
    mkMyClass = unsafePerformIO $ do
       newObj <- cppNewMyClass
       fPtr <- newForeignPtr cppDeleteMyClass newObj
       return $ MyClass fptr
    

    I'm not quite sure about those FunPtrs, hopefully somebody will comment something about that...