JNAerator und time_t

Nachtrag zu neulich: JNAerator macht aus einem time_t in etwa Folgendes (schon an Java Naming Conventions angepasst):

Damit kann man fast nichts anfangen; insbesondere bekommt man den Eigentlichen Timestamp nicht da raus. Das “Pointer” ist außerdem irreführend, denn C liefert an der Stelle keinen Pointer, sondern die Sekunden seit Epoch. Funktionieren tut:

Quelle, hth

JNAerator?

Das Anstrengende Fehleranfällige an JNI ist das Mapping von (komplexen) Datentypen. Nun gibt es aber auch noch JNA,

a community-developed library that provides Java programs easy access to native shared libraries without using the Java Native Interface.

(Wikipedia), und dafür insbesondere den “JNAerator“, der komplexe Datentypen “automatisch” mappt. Die Idee liegt nahe, statt JNI(-Strukturen) JNA(-Strukturen) zu nutzen. Im Folgenden ein Versuch.

Die Installation ist so weit klar:

Der Aufruf allerdings schon nicht mehr so sehr (“0.13” und den Pfad zum lokalen Maven-Repo ggf. anpassen):

Wichtig ist nämlich -runtime JNA, mit den default Einstellungen handelt man sich Abhängigkeiten zu org.bridj (laut Doku “faster runtime that supports C++”) und damit die Notwendigkeit ein, die dll darüber zu laden. Und das will man zumindest dann nicht, wenn man plain JNI möchte.

Das JNAerator Studio (hier) scheint das übrigens per default anders zu machen, aber das nur am Rande. Trotzdem für die Akten: Den Pfad zur dll würde man BridJ so bekannt machen:

Aber zurück zu den so kompilierten Klassen. -mode Directory erzeugt sie als .java-Dateien, nicht als .jar. Das ist hilfreich, denn man muss sie editieren, siehe unten. Sie enthalten (übrigens neben Kommentaren mit dem vollen Pfad der Quelldatei, im Beispiel also /some/path/to/some/HeaderFile.h, evt. will man den nicht öffentlich machen) dann alle structs in ihrer Java Version. Das eigentliche Interface, das die Methoden der Header-Datei bereitstellt, muss man trotzdem selber schreiben und wie folgt laden:

In MyInterface deklariert man die Methoden, auf die man in der dll zugreifen möchte. Primitive Übergabe-/Rückgabeparameter sind dabei ziemlich selbsterklärend (int=int, bool=boolean, etc.), Strings sind char-Arrays, Pointer bringt JNA mit (bsplw. IntByReference, IMHO ein Vorteil gegenüber JNI), structs hat man sich ja eben in Java-Klassen übersetzt. Also alles gut? Tatsächlich gibt es Probleme mit den erzeugten Klassen:

Einige sind offensichtlich, bsplw. überschreiben sie

mit

, was natürlich nicht kompiliert. Globales Suchen-und-Ersetzen, fertig.

Anderes ist schon komplexer: Structures (also die Java-Klasse jetzt) haben ein Alignment, im Konstruktor zu übergeben. Wenn ich das richtig verstehe, korrespondiert das mit der entsprechenden Compiler-Option in VS und sollte automatisch erkannt werden? Oder es hängt an der Plattform? Wie auch immer: Wenn man eine struct hat, die, sagen wir, 22 Byte belegt, dann ist es nicht hilfreich, wenn der default von 4 Byte großen Blöcken ausgeht. Entweder werden dann nämlich zwei Byte abgeschnitten, oder zwei unnötige Bytes angehängt, was zu Verschiebungen in der Folgestruktur führt!

Beispiel: In Arrays von structs beginnt der zweite Eintrag dann zwei Bytes zu spät (es fehlen zwei Bytes zu Beginn) oder zu früh (der Eintrag beginnt mit den letzten zwei Bytes aus dem ersten Eintrag). Nicht unbedingt einfach zu debuggen m( Hilfreich ist es, das System-Property jna.dump_memory auf true zu setzen, und die Ausgabe in einen Hex-Editor zu kopieren. Man sieht die beschriebenen Byte-Offsets dann wenigstens (Danke, Nils!).

Ich habe dazu eine eigene Zwischenschicht eingezogen:

Zusammenfassung

Ja, JNAerator erzeugt einem Java-Klassen aus Headerdateien. Und das ist bei sehr großen Headern auch hilfreich. Aber es ist auch sehr fehleranfällig – und teilweise sogar fehlerhaft. Es kann nur eine Basis für eigenen Code sein.

Update

Nachtrag zu time_t