JNI: Shared Libraries mit dem Java Native Interface nutzen
Plattform spezifische Schnittstellen lassen sich mit dem Java Native Interface nutzen. So lässt sich Funktionalität implementieren, die vom Betriebssystem oder auch Hardware abhängig sind.
Das Java Native Interface schlägt eine Brücke zu Plattform spezifischen Schnittstellen. Dazu ist es ähnlich wie bei nativen NodeJs Modulen notwendig in C/C++ geschriebene Funktionen und Methoden mit speziell definierten Datentypen für Parameter und Rückgabewerte zu schreiben. Diese können dann in Java Methoden aufgerufen werden. Zudem kann die Java VM im C/C++ Code referenziert werden und es können Java Objekte instanziiert werden und Methoden der Objekte wie auch Methoden aus einem statischen Kontext können aufgerufen werden.
Eine CLI Anwendung zur Maussteuerung
Als Beispiel Anwendung soll eine Kommandozeilenanwendung zur Maussteuerung dienen. Diese wird für Windows entwickelt und greift auf Funktionen der WinAPI zurück. Hierbei habe ich allerdings darauf geachtet, dass ich einen Compiler einsetze, der Plattform unabhängig ist und mit ein wenig Rechereche diese Anwendung auch auf anderen Betriebssysteme portiert werden kann. Zumindest für Linux weiß ich das es eine Tatsache ist.
Eine Schnittstelle zwischen Java und nativem Code
Um eine Klasse auf Funktionen und Methoden nativer Bibiliotheken verweisen zulassen müssen wir drei Dinge wissen:
Die loadLibrary(String name) Methode der System Klasse lädt die Shared Library aus dem Verzeichnis, das in der Option library.path angegeben wurde. Das Argument ist der Dateiname ohne Dateierweiterung. Die Erweiterung ändert sich je nach Betriebssystem ohnehin.
Sie wird im static Block der Klasse aufgerufen. Dieser wird nur beim erstmaligen Laden der Klasse in den Speicher noch vor dem Konstruktor ausgeführt.
Das Schlüsselwort native gibt dem Java Compiler auskunft, das die Methode in der geladenen Bibliothek zu finden ist. Diese Methoden haben daher auch keinen Body.
C Header Dateien erzeugen
Man könnte die Header Definitionen auch einfach selbst schreiben, allerdings bietet der Java Compiler die Möglichkeit diese für uns aus einer Klasse zu erzeugen.
Dies funktioniert mit folgendem Parametern, die dem Java Compiler übergeben werden:
Dadurch wird folgende Header Datei im Ordner src\main\native\win32 erzeugt.
Implementation der Header Datei
Nun können wir die einzelnen Funktionen aus der Header Datei implementieren. Wir greifen dabei auf die Funktionen SendInput() und GetCursorPos() der Windows API zurück.
Java's primitive Datentypen können mit ihrem nativen Gegenstücken nach belieben hin und zurück gecasted werden.
Type casting ist eine expliziete Typkonvertierung.
Komplizierter wird es mit allem was nicht auf dem Stack gespeichert wird.
Nach dem alle Funktionen implementiert wurden, kann man mit dem GCC Compiler nun eine Shared Library kompilieren.
Die Point Klasse ist simpel strukturiert. Sie hat 2 Eigenschaften: X und Y. Beide sind vom Typ long und als final deklariert. Der Konstruktor wird überladen. Die erste Variante nimmt einen String an, welcher in X und Y geparsed wird. Die zweite nimmt schlicht Werte für X und Y entgegen.
NbBufferReader Klasse
Ein normaler Scanner würde den Thread blockieren. Das würde den gewünschten Programmfluss behindern. Daher implementieren wir eine Klasse, die in einem extra Thread auf eine Eingabe wartet. Wird eine neue Zeile in die Eingabe geschrieben, so wird diese in eine Queue abgelegt. Dort kann sie dann im Laufe des Main-Loops herausgeholt werden.
Das Schlüsselwort volatile bedeutet in Java, dass auf die Variable Thread übergreifend zugriffen wird. In C und C++ bedeutet es allerdings, dass der Zustand der Variable flüchtig ist und sich ohne expliziten Zugriff aus dem Source Code ändern kann.
Test Klasse
Abschließend bleibt nur noch die Implementation der einzelnen Klassen in einer Anwendung.