Suppose a c library has two functions to initialize the library and to perform clean up before program exit. How do I integrate those two function into haskell using ffi? The initialize function should be called before calling any other functions in the library and the cleanup function should be call right before the program exit.
I think the only way is to just make two IO actions for the initialization and termination. That is how the GLFW-b package does it. It has init and terminate.
Actually I think foreignptr and unsafeperformio could do this.
Suppose the initialize function return a null pointer, you can use this with unsafeperformio to create a global variable that will be reference by all foreign call to the c library. The clean up function will be called
when no more foreign call to the c library will be made.
I don’t know how correct I’m and this method seem hacky. That’s why I came here to ask if anybody have a better solution.
That sounds like it might work. One problem might be that top-level bindings are never garbage collected, so don’t let your termination mechanism depend on that.
Definitely hacky for no good reason. Explicit init/terminate is way better and allows more scope control, that can be handy in e.g. tests.
I see bracket
was not mentioned. Is it not a fitting solution to this problem?
The problem with the init
terminate
pair that bracket
solves is that an exception may occur between them — bracket
makes sure that terminate
is still called.
I am not experienced with foreign IO
calls. Is there some bit of knowledge that I am missing?
Hey, I found a way to automatically call the clean up function, at least on posix compliant system. You can register a function to be invoke when the program exit with the atexit function.
I’ve just tested this with haskell ffi.
There’s (AFAIK) no way to guarantee some function is executed at the end of some program: for example, a SIGKILL
from the outside or call to _exit(2)
could cause this.
So, it’s kinda weird for a library to have a “cleanup” function that must be called. What would such function do that isn’t covered by the program simply exiting? Is it meant to clean up things while the process keeps on running, but the library won’t be used anymore? In said case, atexit
or similar doesn’t really help, you could as well not call the function.
Using bracket
as suggested by @kindaro earlier would be the right approach, if indeed the cleanup function must be called in a “best effort” way:
import Control.Exception.Base (bracket_)
foreign import capi safe "mylibrary.h initMyLibrary"
initMyLibrary :: IO ()
foreign import capi safe "mylibrary.h cleanupMyLibrary"
cleanupMyLibrary :: IO ()
withMyLibrary :: IO a -> IO a
withMyLibrary = bracket_ initMyLibrary cleanupMyLibrary
main :: IO ()
main = withMyLibrary $ do
codeUsingMyLibrary
Of course, in this case, one could all codeUsingMyLibrary
in some code where initMyLibrary
wasn’t called. There are ways around this, at the cost of API complexity and developer experience, so it’s most of the time not worth the hassle.
What if we make withMyLibrary
have type ReaderT FancyToken IO α → IO α
instead? Here, FancyToken
is a type of which no values can be constructed by the user, and all library functions that must be run inside of a library bracket require it. So, there is no way to run library functions outside of a library block.
This is only a slight complication for the user — they need to add liftIO
whenever they use standard IO functions inside a library block. At the same time, it reduces cognitive effort, because the user does not need to worry whether library functions are called inside a library bracket — mistakes will be pointed out by the compiler.