Dwi Prasetyo Adi Nugroho
Dwi Prasetyo Adi NugrohoData Engineer

Koduj w Javie, uruchamiaj w C++ dzięki GraalVM

Dowiedz się, jak używać kodu napisanego w Javie w C++. Użyj w tym celu GraalVM i obrazów natywnych, które pozwolą na uruchomienie Javy bez JVM.
20.04.20205 min
Koduj w Javie, uruchamiaj w C++ dzięki GraalVM

Java i C ++ pozostają jednymi z najbardziej popularnych języków programowania. Oba są inaczej zaprojektowane i różnią się charakterystyką oraz, w zależności od problemu, jeden może działać lepiej od drugiego. Trzeba je jednak w pewnym momencie zintegrować. Dotyczy to, np. wywołania metody Javy w C++. Konieczność integracji tych dwóch języków nie jest jednak niczym nowym, czego przykładem jest ten artykuł z 1996 roku. Od tamtego czasu oba te języki niezwykle się rozwinęły. 

Tutaj nauczymy się, jak wywoływać metodę Javy z C++ przy użyciu natywnego obrazu GraalVM. Jeśli po raz pierwszy usłyszałeś o GraalVM, to być może zainteresuje Cię strona internetowa tego projektu. Jest to maszyna wirtualna, której można używać do uruchamiania aplikacji Java. Opiera się ona na JVM, ale jest rozszerzona o dodatkowe funkcje, np. o kompilację z wyprzedzeniem, zmniejszone zapotrzebowanie na pamięć i inne optymalizacje.

Co więcej, GraalVM zapewnia wtyczkę do natywnych obrazów, której można użyć do skompilowania aplikacji Java do aplikacji natywnej. Oznacza to, że aplikacja może działać bez konieczności instalowania i uruchamiania maszyny JVM. Co ciekawe, możesz także użyć tej wtyczki, aby skompilować aplikację Java jako współdzieloną bibliotekę i załadować ją z kodu C++. Przydaje się to gdy piszesz głównie w C++, ale chcesz użyć istniejącej biblioteki Java na jakiejś jej części.

Jak to działa?

Zanim przejdziemy do konkretów, zobaczmy, jak to w ogóle działa. Właściwie to będziemy robić następujące rzeczy:

  1. Napiszemy metodę w Javie.
  2. Skompilujemy kod Javy do współdzielonej biblioteki C++ (yourlib.so) i nagłówka (yourlib.h) przy użyciu GraalVM.
  3. Załadujemy bibliotekę i nagłówek do projektu C++.


W przykładzie tym zasymulujemy sytuację, w której chcesz użyć funkcji zewnętrznej biblioteki Java w projekcie C++. Jesteśmy szczególnie zainteresowani użyciem jednej z funkcji matematycznych z biblioteki Google Guava.

Przygotowywanie projektu w Javie

Najpierw musimy skonfigurować projekt maven dla naszej javowej metody. Budowanie naszego projektu w maven pomaga nam zaimportować zależności, a także wykorzystać wtyczkę GraalVM do stworzenia biblioteki współdzielonej. Po utworzeniu projektu dodaj zależności do biblioteki Guava (lub innej biblioteki, której potrzebujesz) i bibliotekę GraalVM do pliku pom.xml:

<dependencies>
    <!-- https://mvnrepository.com/artifact/org.graalvm.nativeimage/svm -->
    <dependency>
        <groupId>org.graalvm.nativeimage</groupId>
        <artifactId>svm</artifactId>
        <version>19.3.1</version>
    </dependency>
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>28.2-jre</version>
    </dependency>
</dependencies>


Potem dodajemy następującą wtyczkę, aby zbudować kod Java jako bibliotekę C++:

<build>
    <finalName>libmymath</finalName>
    <plugins>
        <plugin>
            <groupId>com.oracle.substratevm</groupId>
            <artifactId>native-image-maven-plugin</artifactId>
            <version>20.0.0</version>
            <executions>
                <execution>
                    <goals>
                        <goal>native-image</goal>
                    </goals>
                    <phase>package</phase>
                </execution>
            </executions>
            <configuration>
                <buildArgs>--shared -H:Name=libmymath</buildArgs>
            </configuration>
        </plugin>
    </plugins>
</build>


Spójrzmy na <finalName>libmymath</finalName>, <version>20.0.0</version> oraz <buildArgs>--shared -H:Name=libmymath</buildArgs>. W tagu <finalName> wpisz nazwę swojej biblioteki. Zwróć uwagę na wersję podaną w znaczniku <version>, ponieważ będzie ona potrzebna później. W <buildArgs> określamy parametr shared oraz name, aby poinstruować GraalVM, do stworzenia biblioteki zamiast pliku wykonywalnego. Wpisz taką samą nazwę jak finalName w parametrze Name.

Teraz przygotujmy naszą metodę w Javie. Przyjmując na wejście liczbę całkowitą x, jesteśmy zainteresowani obliczeniem najbliższej potęgi o wartości o 2 większej od x. Na przykład, jeśli x wynosi 14, to zwróci 16, ponieważ 16 jest najbliższą potęgą o 2 większą niż 14. Poniżej znajduje się fragment naszej funkcji. Zwróć uwagę, że musimy umieścić dodatkowy parametr, który jest wątkiem typu IsolateThread, aby połączyć ze sobą wykonanie kodu w Java i C++. Parametr ten musi znajdować się na pierwszej pozycji. Co więcej, trzeba określić nazwę metody w adnotacji CEntryPoint. Będzie to nazwa metody, do której odwołujemy się w naszym kodzie C++. Możesz sprawdzić kompletny projekt Java w tym repozytorium.

import com.google.common.math.IntMath;
import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.c.function.CEntryPoint;

public class MyMath {
    @CEntryPoint (name = "ceilingPowerOfTwo")
    public static int ceilingPowerOfTwo(IsolateThread thread, int x) {
       return IntMath.ceilingPowerOfTwo(x);
    }
}

Przygotowywanie GraalVM

Po przygotowaniu naszego projektu musimy uruchomić mvn package, aby skompilować naszą metodę Java do biblioteki współdzielonej. Ponieważ funkcja ta jest dostępna tylko w GraalVM, będziemy musieli skonfigurować GraalVM i skierować nasz JAVA_HOME na binarkę GraalVM. To dlatego trzeba wykonać następujące czynności:

  1. Pobierz GraalVM. Upewnij się, że masz właściwą architekturę, wersję Javy oraz wersję GraalVM podaną w pliku pom.xml. W tym przykładzie użyjemy GraalVM 20.0.0 dla Java 8.
  2. Zainstaluj rozszerzenie native-image za pomocą pliku binarnego gu. W środowisku uniksowym możesz to zrobić za pomocą: <path_to_graalvm_directory>graalvm-ce-java8–20.0.0 2bin/gu install native-image.
  3. Zaktualizuj JAVA_HOME, aby wskazywał pobrany katalog GraalVM. W środowisku uniksowym możesz użyć eksportu JAVA_HOME=<path_to_graalvm_directory/graalvm-ce-java8–20.0.0 2/>.

Kompilacja biblioteki współdzielonej

Możesz teraz wywołać instalację mvn package, aby zbudować bibliotekę. Wygeneruje to kilka plików, a mianowicie: graal_isolate.h, graal_isolate_dynamic.h, libmymath.h, libmymath_dynamic.h i libmymath.so (lub w innych formatach w innym systemie operacyjnym). Poza naszą własną biblioteką wygenerowana zostanie również biblioteka Graal VM C++.

Importowanie biblioteki do kodu C++

Skorzystajmy teraz z naszej biblioteki w prostym projekcie C++. Umieśćmy wygenerowany plik w docelowym katalogu tego projektu C++. Możesz na przykład utworzyć folder include i umieścić w nim swoje pliki.

#include <iostream>
#include <libmymath.h>

int main() {
    graal_isolate_t *isolate = NULL;
    graal_isolatethread_t *thread = NULL;

    if (graal_create_isolate(NULL, &isolate, &thread) != 0) {
        fprintf(stderr, "initialization error\n");
        return 1;
    }

    printf("Result> %d\n",ceilingPowerOfTwo(thread, 14));
    return 0;
}

Skompiluj kod C++ za pomocą następującego polecenia i spróbuj go uruchomić.

g++ ceilingPowerOfTwoCpp.cpp -L includes/ -I includes/ -lmymath -o ceilingPowerOfTwoCpp

Uwaga: może być konieczne ustawienie katalogu includes na LD_LIBRARY_PATH przy użyciu:

export LD_LIBRARY_PATH=<path_to_includes_directory>

Podsumowanie

Interoperacyjność jest nieuniknionym wymogiem w świecie, gdzie języki programowania rozwijają się równocześnie. Funkcja współdzielonej biblioteki GraalVM Native Image zapewnia łatwy sposób osadzenia metody Javy w programie C++. Umożliwia to pisanie złożonych funkcji zdefiniowanych przez użytkownika oraz korzystanie z zewnętrznych bibliotek. Dobrym przykładem może być opracowanie frameworku do przetwarzania danych, który zapewnia API, które jest zarówno wysokopoziomowe, jak i wysoce wydajne.

Jeśli chcesz dowiedzieć się więcej o GraalVM, zachęcam do odwiedzenia strony projektu. Pełny kod z tego artykułu jest dostępny tutaj.

Oryginał tekstu w języku angielskim przeczytasz tutaj.

<p>Loading...</p>