Allgemeine Informationen
Ich experimentiere derzeit mit dem C->Haskell (C2HS) Interface Generator für Haskell. Auf den ersten Blick war es einfach großartig, ich habe eine ziemlich komplizierte C++-Bibliothek (mit einer kleinen extern C
-Wrapper) in nur ein paar Stunden. (Und ich habe noch nie ein FFI gemacht.)
Es gab nur ein Problem: Wie bekommt man den in der C/C++-Bibliothek zugewiesenen Speicher wieder frei? Ich fand {#pointer ... foreign #}
im C2HS-Dokumentation und das sieht genau nach dem aus, was ich suche. Da mein C-Wrapper die C++ Bibliothek in eine Bibliothek mit referenzieller Transparenz mit einer funktionalen Schnittstelle verwandelt, sollte der Haskell Storage Manager in der Lage sein, die harte Arbeit für mich zu erledigen :-). Leider ist es mir nicht gelungen, dies zum Laufen zu bringen. Um mein Problem besser zu erklären, habe ich ein kleines Demo-Projekt auf GitHub die die gleichen Eigenschaften wie die C/C++-Bibliothek+Wrapper hat, aber nicht den Overhead. Wie Sie sehen können, ist die Bibliothek völlig sicher in der Verwendung mit pure unsafe
FFI.
Demo-Projekt
Auf GitHub habe ich eine kleines Demo-Projekt wie folgt organisiert:
C-Bibliothek
Die C-Bibliothek ist sehr einfach und nutzlos: Man kann ihr eine Ganzzahl übergeben und erhält so viele Ganzzahlen (derzeit [0..n]
) aus der Bibliothek zurück. Denken Sie daran: Die Bibliothek ist nutzlos, nur eine Demo. Auch die Schnittstelle ist recht einfach: Die Funktion LTIData lti_new_data(int n)
gibt (nach Übergabe einer ganzen Zahl) eine Art undurchsichtiges Objekt zurück, das die von der C-Bibliothek zugewiesenen Daten enthält. Die Bibliothek hat auch zwei Accessor-Funktionen int lti_element_count(LTIData data)
y int lti_get_element(LTIData data, int n)
gibt ersteres die Anzahl der Elemente und letzteres Ihr Element zurück n
. Ah, und zu guter Letzt sollte der Benutzer der Bibliothek nach der Benutzung die undurchsichtige LTIData
mit void lti_free_data(LTIData data)
.
Haskell-Bindung auf niedriger Ebene
Die Low-Level-Haskell-Bindung wird mit C2HS eingerichtet, Sie finden sie in
Haskell-API auf hoher Ebene
Zum Spaß habe ich auch eine Art Haskell-API auf hoher Ebene unter Verwendung der Low-Level-API-Bindung und einer einfaches Treiberprogramm die die hochrangige API verwendet. Mit dem Treiberprogramm und z.B. valgrind kann man leicht den ausgelaufenen Speicher sehen (für jeden Parameter p_1, p_2, ..., p_n
die Bibliothek tut \sum_{i = 1..n} 1 + p_i
Zuweisungen; leicht zu beobachten wie unten):
$ 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
Aktueller Stand der Demo
Sie sollten in der Lage sein, das Programm zu klonen, zu kompilieren und auszuführen. Projekt durch einfache Eingabe von git clone https://github.com/weissi/c2hs-experiments.git && cd c2hs-experiments && cabal configure && cabal build && dist/build/TestHsLTI/TestHsLTI
Was ist also das Problem?
Das Problem ist, dass das Projekt nur Foreign.Ptr
und nicht die "verwaltete" Version Foreign.ForeignPtr
unter Verwendung der C2HS {#pointer ... foreign #}
und ich kann es nicht zum Laufen bringen. Im Demoprojekt habe ich auch eine .chs
Datei, die versucht, diese fremden Zeiger zu verwenden aber es funktioniert nicht :-(. Ich habe es sehr hart versucht, aber ich hatte keinen Erfolg.
Und eine Sache verstehe ich auch nicht: Wie kann ich GHC mit C2HS sagen, wie man die Daten der Bibliothek freigibt. Die Bibliothek des Demoprojekts bietet eine Funktion void lti_free_data(LTIData data)
die aufgerufen werden sollte, um den Speicher freizugeben. Aber GHC kann das nicht erraten!?! Wenn GHC regelmäßig ein free()
wird nicht der gesamte Speicher freigegeben :-(.