C-Libraries in Java nutzen 2: Funktionen mit veränderlichen Parametern
C-Libraries in Java nutzen 2: Funktionen mit veränderlichen Parametern | heise online
heise+ entdecken
Abo
Alle Magazine im Browser lesen
Newsletter
heise-Bot
Push
-Nachrichten
Anzeige
close notice
This article is also available in
English
.
It was translated with technical assistance and editorially reviewed before publication.
Don’t show this again
.
Javas Foreign Function & Memory API (FFM) dient dazu, auf Code in einer Shared Library beziehungsweise DLL zuzugreifen, der in einer Programmiersprache wie C oder Rust geschrieben ist. Allerdings muss der Code dazu einige Voraussetzungen erfüllen. Diese dreiteilige Artikelserie zeigt anhand
einer in C geschriebenen Demo-Library
, wie eine Java-Anwendung die Funktionen der Bibliothek aufruft, welche Vorbereitungen erforderlich sind und welche Regeln zu beachten sind.
Weiterlesen nach der Anzeige
Rudolf Ziegaus ist Software-Entwickler, Java-Trainer und Geschäftsführer der IO Software GmbH. Seine Lieblingsthemen sind PKi, Kryptographie und systemnahe Programmierung.
Nachdem
der erste Teil gezeigt hat
, wie man in Java eine in C geschriebene Shared Library lädt und einfache Funktionen dieser Shared Library aufruft, geht es jetzt um komplexere Szenarien. Er zeigt, wie man aus Java Funktionen mit veränderbaren Parametern aufrufen und Arrays sowie Strukturen übergeben kann.
Grundlagen der Foreign Function & Memory API
Funktionen mit veränderlichen Parametern
Funktionen mit veränderbarem Parameter
Die bisherigen Beispiele haben die Aufrufe der nativen Funktionen einfach gehalten. Die Java-Anwendung hat lediglich Parameter durchgereicht und den Rückgabewert übernommen.
Anders sieht es bei den nächsten Beispielen aus. Als erstes folgt die einfache C-Funktion
getVersion2
, die wie die Funktion
getVersion
aus Teil 1 die Version der Library ermittelt. Die neue Funktion gibt die Versionsnummer aber nicht als Wert zurück, sondern verändert dazu einen Parameter. Das funktioniert in C, indem eine Anwendung für einen Parameter nicht den Wert selbst, sondern dessen Adresse übergibt (Call by Reference). Dieses Konstrukt sieht in C folgendermaßen aus:
EXPORT void getVersion2(int* version);
Folgender Java-Code ruft die Funktion auf:
Weiterlesen nach der Anzeige
int version;
getVersion2(&version);
Das
&
kennzeichnet in C, dass die Funktion die Adresse der Variablen nutzt. Java erlaubt das Vorgehen nicht, sodass ein Rückgabewert unerlässlich ist. Folgende Java-Methode verwendet die C-Funktion mit Referenz:
public int getVersion2() throws Throwable
{
MethodHandle method = getMethodHandle("getVersion2",
FunctionDescriptor.of(
ValueLayout.JAVA_INT,
ValueLayout.ADDRESS
));
try (Arena arena = Arena.ofConfined())
{
MemorySegment versionSeg =
arena.allocate(ValueLayout.JAVA_INT.byteSize());
method.invoke(versionSeg);
int version = versionSeg.get(ValueLayout.JAVA_INT, 0);
return version;
}
}
Zuerst ruft der Code wie im ersten Teil der Serie wieder die Methode
getMethodHandle()
auf. Der Aufruf definiert den
FunctionDescriptor
für die Funktion
getVersion2()
.
Die Angabe
ValueLayout.ADDRESS
für den Parameter zeigt an, dass die C-Funktion eine Adresse erwartet.
Videos by heise
mehr Videos
c't 3003
heise & ct
Peertube
Jetzt kommt der spannendere Teil: Um eine Adresse übergeben zu können, muss die Java-Anwendung mittels der FFM-API einen Speicherbereich von vier Byte (für den Datentyp
int
) reservieren. Das geschieht mit einer Arena, die er erste Teil bereits erläutert hat. Das Erzeugen der Arena mit dem
try-with-ressources
-Statement stellt sicher, dass die Arena nach dem
try
-Block automatisch geschlossen und der darin verwaltete Speicher automatisch freigegeben wird. Es gibt verschiedene Typen von Arenas – die im Beispiel über
ofConfined
erzeugte sorgt dafür, dass die Anwendung nur auf Speicher des aktuellen Thread zugreifen kann. Eine mit
ofConfined()
erzeugte Arena – beziehungsweise der damit allozierte Speicher – ist daher nicht threadsicher.
Als nächstes gilt es, den erforderlichen Speicherbereich für den Parameter
version
zu allokieren. Dafür besitzt die Arena die Methode
allocate()
. Die Größe des benötigten Speichers kann man durch die Funktion
byteSize()
für die Variable ermitteln. Hier sei nochmals darauf hingewiesen, dass der Wert die Größe des Java-Datentyps darstellt und nicht zwingend etwas über den C-Datentyp aussagt. Da die C-Funktion einen
int
-Parameter entgegennimmt, sind wir auf der sicheren Seite, da
int
in C stets vier Byte umfasst. Bei einem
long
-Wert in C hängt die Größe dagegen von der Plattform ab.
Der Speicherbereich wird durch ein
MemorySegment
dargestellt, das beim Aufruf der Methode
invoke
an die C-Funktion weitergereicht werden muss.
Anschließend kann die Anwendung das Ergebnis auslesen. Dazu ruft sie auf dem
MemorySegment
die Funktion
get
auf und übergibt ihr das Layout des Speichers (in diesem Fall ein
JAVA_INT
) und den Offset zum Lesen aus dem
MemorySegment
. Für das Beispiel ist der Offset null. Durch die Angabe
JAVA_INT
gibt die Funktion einen
int
-Wert zurück, den die Anwendung weiterverarbeiten kann.
Funktionen mit einem Array-Parameter
Die nächste Aufgabe baut auf dem Vorgehen auf, verarbeitet aber nicht nur einen Wert, sondern ermittelt den Durchschnitt aus einer Liste von
int
-Werten. Dazu muss sie der nativen Funktion ein Array von
int
-Werten übergeben:
public double calcAverage(int [] values) throws Throwable
{
MethodHandle calcAverage =
getMethodHandle("calcAverage"),
FunctionDescriptor.of(
ValueLayout.JAVA_DOUBLE, // return value
ValueLayout.ADDRESS, // data values
ValueLayout.JAVA_INT)); // number of elements
try(Arena arena = Arena.ofConfined())
{
long totalSize = ValueLayout.JAVA_INT.byteSize() * values.length;
MemorySegment valueSegment = arena.allocate(totalSize);
for (int i = 0; i < values.length; i++)
{
valueSegment.setAtIndex(ValueLayout.JAVA_INT, i, values[i]);
}
double result = (double) calcAverage.invoke(valueSegment,
values.length);
return result;
}
}
Zunächst berechnet der Code die gesamte Speichergröße des Arrays (
totalSize
) und reserviert den benötigten Speicher mit
allocate()
. Anschließend belegt der Code den Speicher mit der Methode
setAtIndex
für das jeweilige
MemorySegment
. Der Aufruf erfolgt für jedes Element des Arrays.
Schließlich ruft der Code die Methode
invoke
für den
MethodHandle
auf und übergibt ihr als Parameter das Array und dessen Länge. Schließlich gibt sie das Ergebnis der C-Funktion zurück.
Anzeige
Anzeige
Newsletter
heise-Bot
heise-Bot
Push Nachrichten
Push
Push-Nachrichten
kopieren
5 Monate heise+ lesen & 50 % sparen!
5 Monate heise+ lesen & 50 % sparen!
50 % Rabatt für 5 Monate heise+ – mit allen Inhalten auf heise online sowie digitalem Zugriff auf alle Magazine des heise-Kosmos.
50 % Rabatt für 5 Monate heise+ – mit allen Inhalten auf heise online inklusive Tests, Ratgebern und tiefgehende Hintergründe sowie digitaler Zugriff auf alle Magazine des heise-Kosmos.
Jetzt sichern
← Volver a las noticias