Orrr ne, bitte kein Windows Support!

Immer wieder wurde an mich herangetragen, dass ich doch checkit_tiff für Windows bereitstellen möge. Mich nervt Windows und ich mag nicht Microsoft unterstützen.

checkit_tiff ist ein freies Werkzeug und es sollte nicht in Käfigen gehalten werden. Im Ernst, Windows hat soviele Eigenheiten und man muss etliches an Sonderregeln implementieren, damit man das vernünftig kompiliert bekommt. Im Code von checkit_tiff gibt es auch genügend Hinweise, dass jeder, der möchte, sich das Programm für Windows kompilieren kann. Aber Support mag ich nicht auch noch anbieten.

Wie auch immer, da draußen gibt es Menschen, die wollen checkit_tiff nutzen (und dafür ein dickes Danke!). Leider sind sie selten in der Lage es selber zu kompilieren. Und selbst wenn sie jemanden finden, der es für sie kompiliert, fällt es einigen schwer, die Kommandozeile von Windows zu öffnen und das Programm zu benutzen.

Um diesen Menschen zu helfen, trage ich mich schon seit längerem mit dem Gedanken “Hey, da gibt es doch dieses Emscripten, damit kannst Du checkit_tiff nach Javascript transpilen!”. Und damit ging es los.

Emscripten

Emscripten ist ein cooles Projekt. Die Werkzeuge ermöglichen es C und C++ Code nach Javascript (WASM) zu übersetzen. Das ganze kommt wohl aus der Spieleentwicklerecke.

Die Installation ist einfach. Folge den Anweisungen auf emscripten.org. Wenn Du Debian-Nutzer bist, verlasse Dich nicht auf das Debian-Package, da dort einige Zuordnungen fehlerhaft sind.

Bevor man mit Emscripten arbeitet, muss man die Umgebung mit source ./emsdk_env.sh aktivieren. Das sorgt dafür, dass unter Linux die Emscripten-Programme gefunden werden.

Um ein Programm nach Javascript zu kompilieren, ersetzt man in seinem Makefile den C-Compiler durch emcc, und den C++-Compiler durch em++. Fertig.

Für CMAKE Aufrufe sieht dies so aus: cmake -DCMAKE_C_COMPILER=emcc -DCMAKE_CXX_COMPILER=em++

Böse Falle

Der obige Schritt hat erst einmal bei mir funktioniert. Es entstand eine Javascript-Datei und die konnte man mit dem Javascript-Interpreter von NodeJS auch ausführen: js checkit_tiff.js, es funktionierte unter Linux gleichermassen, wie unter Windows. Ziel erreicht?

Nein. Die Falle ist, dass die Architektur von Javascript / Emscripten / NodeJS es nicht ohne weiteres erlaubt, auf Dateien von außerhalb zuzugreifen. Es gibt daher mindestens 3 Dateisysteme, die von Belang sind:

  • memfs, welches “einkompilierte” Dateien abbildet
  • nodefs/noderawfs, welches auf lokale Dateien zugreifen kann, wenn das Javascript innerhalb des standalone nodeJS-Interpreters aufgerufen wird
  • imdbfs, welches im Browser-Sicherheitskontext befindliche Dateien behandelt

Mit der Linker-Option “-s NODERAWFS=1 -l noderawfs.js” klappt dann die Kompilierung nach Javascript und checkit_tiff.js lässt sich auf der Kommandozeile ausführen.

Der leidige Speicher

checkit_tiff ist an sich genügsam. Wenn aber ein ganzes Verzeichnis, oder ein massiv kaputtes TIFF validiert wird, so wird ordentlich Speicher allokiert um die ganzen Ausgaben zu puffern. Beim Kompilieren mittels Emscripten kann es vorkommen, dass beim Ausführen eine Fehlermeldung kommt, dass der Speicher nicht ausreichen würde. Der Grund ist, dass Emscripten versucht in Javascript Speicher als Arrays abzubilden und diese eine vordefinierte Größe besitzen. Mit der Option “-s INITIAL_MEMORY=24051712” habe ich Emscripten angewiesen, mehr Speicher zur Verfügung zu stellen.

Ein Browser ist was völlig anderes!

Gut, das erste Ziel ist erreicht. Jeder kann sich das vorkompilierte Javascript von meiner Webseite holen und muss nix mehr selber kompilieren. Doch ich wollte schon das Javascript in meine Webseite einbinden. Kann ja nicht so schwer sein.

Pustekuchen!

Oben hatte ich ja schon geschrieben, dass es da mehrere “filesystems” gibt. Nach ganz vielem Doku wälzen, war mir klar, man muss da das Browserdateisystem in das memfs mounten. Und die Konfigurationsdateien von checkit_tiff sollten eingebettet werden, damit man die nicht noch irgendwo hinlegen muss.

Hier ist der Aufruf für das Einbetten: “-lidbfs.js –embed-file ${EMBEDDIR}@/”. Die Anweisung sagt nix anderes als: “binde das Browserdatei-FS ein, und nehme alle Dateien aus $EMBEDDIR und mappe die auf ‘/’ des memfs”. Die Variable $EMBEDDIR enthält das Verzeichnis der Konfigurationsdateien mit absolutem Pfad.

Für das Mappen der ausgewählten TIFF-Dateien sorgt dann ein kleines Javascript. Leider klappte das “Mounten” nicht und ich musste auf einen Workaround zurückgreifen, der die Dateien in ein Array kopiert:

function call_checkit_tiff(reader, config, tiff_file) {
    // copy TIFF into MEMFS (NODEjs)
    let result = reader.result;
    const uint8_view = new Uint8Array(result);
    FS.writeFile(tiff_file, uint8_view);
    callMain([config, tiff_file]);
    if (FS.isFile(tiff_file)) {
        FS.unlink(tiff_file);
    }
}

function prepare_checkit_tiff_tiff(input, config) {
    let tiff_files = Array.from(input.files);
    for (i = 0; i < tiff_files.length; i++) {
        let tiff_file = tiff_files[i];
        // prepare copying files to MEMFS and call checkit_tiff
        let reader = new FileReader();
        reader.addEventListener("loadend", function () {
            call_checkit_tiff(reader, config, tiff_file.name);
        });
        reader.readAsArrayBuffer(tiff_file);
    }
}

Webseite gebaut, Seite geladen. Uuups! Die Hilfeausgabe von checkit_tiff wird angezeigt. Hä? Nach Lesen der Doku wird klar, Standardverhalten von Emscripten ist, dass gleich nach dem Laden das Script auch ausgeführt wird. Mit der Option “-s INVOKE_RUN=0” wird das Verhalten abgeschaltet. Jetzt muss explizit `callMain()’ aufgerufen werden (die Kommandozeilenargumente sind über ein Array zu übergeben).

Reload, keine Hilfeausgabe, Button zur Validierung gedrückt. Nix passiert. Erm, doch. Nur eben auf der “Console”. Diese sieht man nur, wenn man im Browser die Entwicklungstools aktiviert. Das ist nicht das erwünschte Verhalten. Wieder Doku ohne Ende gewälzt. Man muss ein PreRun-Module definieren. Das sieht jetzt so aus:

// initialize emscripten runtime correctly
var Module={
 'print': function (text) {
    let p = document.getElementById('out');
    p.appendChild(document.createTextNode(text));
    p.appendChild(document.createElement('br'));
 },
 'printErr': function(text) {
    console.log('stderr: ' + text);
 }
};
Module.logReadFiles=1;

Das, was STDOUT unter Linux ist, ist jetzt ‘print’. Im HTML habe ich jetzt ein Element definiert, welches gesucht wird. Unter dieses Element werden Textknoten für jede einzelne Zeile gehangen, gefolgt von einem Zeilenumbruch. Die Fehlerausgabe (was STDERR unter Linux entspricht) geht weiter in die ‘console’. Übrigens der letzte Eintrag ist zum Debuggen, falls eine Datei nicht gelesen werden kann.

Nun klappt es halbwegs. Es hat noch etwas Javascriptcode benötigt, um die Ausgaben zwischen zwei Checks wieder zu löschen, aber im Wesentlichen war es das.

Bleibt nur noch zu erwähnen, dass wenn man größere Programme nach Javascript transpilieren will, dass man alle abhängigen Bibliotheken ebenfalls mit übersetzt. Es hat sich bewährt, diese so zu konfigurieren, dass nur statisch kompiliert wird.