donderdag 8 maart 2012

Using FFI to use a LibExiv2 in haskell

Haskell has a very well developed Foreign Function Interface. But one of the problems with the FFI is that it is specialized for C.

For a project in our company we needed an alternative to libexif, because the library has problems with reading binary blobs embedded in the exif data.

LibExiv2 is a perfect candidate to use, but gave rise to some technical challenges to overcome. The biggest challenge was, how to deal with auto_ptr's. We couldn't use the auto_ptr to create a StablePtr, which we can use with FFI in haskell.

First some basics of LibExiv2:

Exiv2 can open any image format. They provide a general function for opening files. This is Exiv2::ImageFactory::open. After that we can read the metadata with an object method. readMetaData. This reads the exif data from the image file. After we have done this, we can get an Iterator (some sort of Foldable-like structure) to search for certain keys and their values.

Exiv2::ImageFactor::open gives back an auto_ptr. An auto_ptr is a template class, which behaves like a pointer. But differs when a pointer leaves it scope, then it will be destroyed automagically and more important it is not a real pointer. It only behaves as a pointer, it memory address is stored in the template class.

So when we tried to pass the auto_ptr as StablePtr to the haskell side, we got an segmentation fault. After some research we concluded the following happened:

We retrieved the pointer from the auto_ptr. Then we passed this to the haskell side. But then the auto_ptr vanished from the scope, destroying the pointer in the process. Haskell then passed the original pointer back, but at that time it didn't exist anymore.

The solution was really simple. We created a wrapper around the auto_ptr:



struct Context {
Exiv2::Image::AutoPtr autoptr;

};


By carefully code, we let the auto_ptr never escape an existing scope. And converting only the pointer of the context to a haskell stableptr. This way we could safely send it to the haskell side.

As an example of how to use this trick, I will take some excerpts from the library:


/* This function opens a new image and creates and Exiv2::Image::AutoPtr
then wrap it in a Context to protect it from vanishing
*/
HsPtr openContext(const char * f){
std::string file(f);
Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(file);
Context * c = new Context;
assert(image.get() != 0);
// assign the auto_ptr to the new context
c->autoptr = image;
c->autoptr->readMetadata();
// Cast it to a haskell pointer
return (HsPtr)c;
}

/* Later in the code we can restore the haskell pointer back to a context */

Context * ImagefromStablePtr(HsPtr p){
return ((Context *)p);
}

/* And then use it to get all the keys from the exif data (from haskell to haskell) */

HsPtr getExifKeysIterator(HsPtr p){
assert(p != 0);
Context * ctx = ImagefromStablePtr(p);
// As long as the autoptr is not leaving his context it will exists.
Exiv2::ExifData data = ctx->autoptr->exifData();
ExifIterator * it = new ExifIterator();
size_t size = data.count();
it->data = (char **)calloc(sizeof(char *), size);
it->size = size;
it->it = 0;
size_t counter = 0;
for(Exiv2::ExifData::const_iterator i = data.begin(); i != data.end(); i++){
std::string key = i->key();
it->data[counter] = new char[key.size()+1];
strcpy(it->data[counter], key.c_str());
counter++;
}
return (HsPtr)it;
}




The rest of the job was straightforward, except for the problem to let cabal handle the build process, but I am working on that. Advice would be appreciated.


You can view the code at bitbucket: https://bitbucket.org/eklerks/libexiv2bindings/src

I haven't include the build code. But manual building is not difficult. I will update the repos if I figured out, how to let it work with cabal.

Geen opmerkingen:

Een reactie posten