So, I’m not new to Haskell, but I’ve never worked much with the C FFI. I figured a fun place to start would be adding some really basic Botan.TLS bindings to the botan-bindings package, just to see how rough it would be (to be clear, this isn’t in any sort of shape to be reviewed for inclusion, and I haven’t talked with @ApothecaLabs at all to even see if he’s interested, I’m just doing it to learn, and also my C++ experience is a few decades out of date…).
I wound up with a TLS echo server that works (specific diff against botan is here, initially modeled after the callerback example on the wiki). But, I wound up with a few questions…
1 - How can I pass a (Ptr CString) to a C function? My C bindings are wrapping C++ functions that can raise exceptions, so I’d like to do something like this:
void *allocate(char **errmsg) {
try {
return new Server();
} catch(std::exception &exc) {
*errmsg = new char[strlen(exc.what())+1];
strncpy(errmsg, exc.what(), strlen(exc.what())+1);
return nullptr;
}
}
And then call that giving some sort of errmsg :: Ptr CString, but I don’t have any idea how to allocate that (or how to dereference it after the call). So instead I’m allocating a blank error message string using withCString, giving it a length of 1024, and letting the callback copy the error message into that buffer. I assume there’s a good way to do this, but I’m having no luck figuring it out.
2 - Should I be able to do Network.Socket IO in a haskell callback being called from C++?
Botan.TLS has you set up a Server object with a few callbacks. One of them is emitData, which takes a pointer and a length, and you’re supposed to send that data to the connected client. Easy enough, there’s a Network.Socket.sendBuf that does exactly that, but when I call it from a callback, the resulting “bytes written” was always 0. I’m guessing that the async IO doesn’t run while C callbacks are being called? This was in ghci, so maybe a fully compiled and threaded executable would have worked better, but I’d like to not have something totally broken in ghci anyhow. I kludged around it by copying the Ptr/len to a ByteString and throwing that into a queue to be handled after the callback, which is fine but I wanted to make sure I wasn’t missing something obvious.
3 - How do I clean up?
I think the normal pattern for cleaning up something that allocates is to wrap the Ptr into a ForeignPtr that tracks the deallocate function. But that function itself is a FunPtr, and I think those need to be freed using freeHaskellFunPtr? So maybe all the allocated data and callback functions just need to be freed in a normal bracket pattern, and that’s fine, but I’m still confused about how a ForeignPtr can ever be safely used. Or am I misunderstanding a whole lot of stuff there?
Thanks for any help!