Sytuacja kobiet w IT w 2024 roku
19.02.20205 min

Assembly w Javie w C

Zobacz, jak możesz wywołać kod Assemblera z Javy, wykorzystując do tego C. (nie wiemy po co, ale fajny trick)

Assembly w Javie w C

Wyjaśnienie: Będę używał Windowsa i Visual C++ z jego Inline Assember. Jeśli ktoś z Was używa MacOs albo Linuxa, to demonstracje będą się różnić od tego, co jest w artykule. Wszystko, co znajduje się poniżej ma jedynie na celu pokazanie pewnych rzeczy. 

Wstęp

Java jest dojrzałym i samowystarczalnym językiem programowania, chociaż wszyscy wiemy, że można ją “połączyć” z C (przez Java-Native-Interface), aby przyspieszyć działanie najważniejszych części kodu. 

W przypadku C i C++ jest również możliwe delegowanie niektórych jeszcze bardziej krytycznych części kodu bezpośrednio do Assembly. 

Chcę tutaj pokazać, w jaki sposób ta Matrioszka Javy, C i Assembly może wyglądać. Miejcie jednak świadomość tego, że nasz przykład jest dosyć prosty, zatem może nie być z niego pożytku przy realnych projektach. 

W naszym przykładzie:

  • mamy program lini poleceń w Javie
  • uruchomimy ten program dając mu 2 liczby całkowite jako argumenty (z obsługą błędów po stronie klienta)
  • główna logika biznesowa to metoda “sum”, a to z kolei kluczowy elementem programu, który chcemy przyspieszyć z C i Assembly. 

Java

Konfiguracja

Najpierw musimy ściągnąć JDK.

Moja wersja jest już dosyć stara, ale śmiało, instalujcie tę nowszą. 

Po instalacji, sprawdźcie, czy wszystko na pewno działa.

>java -version
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) Client VM (build 25.121-b13, mixed mode, sharing)


Kodowanie

Oto nasz program: funkcja main, parsowanie argumentów i nasza docelowa metoda “sum” (z jej implementacją w Javie):

public class Test {
 
    public static void main(String[] args) {
  
        if (args.length != 2) {
            System.out.println("Error: wrong params count");
            return;
        }
  
        int a;
        try {
            a = Integer.parseInt(args[0]);
        } catch (Throwable throwable) {
            System.out.println("First param is not a number");
            return;
        }
  
        int b;
        try {
            b = Integer.parseInt(args[1]);
        } catch (Throwable throwable) {
            System.out.println("Second param is not a number");
            return;
        }
  
        Test test = new Test();
        System.out.println(test.sum(a, b));
    }
 
    public static int sum(int a, int b) {
        return a + b;
    }
}


Kompilacja

W celu uruchomienia programu, musimy go najpierw skompilować kompilatorem Javy. Wygeneruje to plik binarny Test.class, którym zajmiemy się później.

> javac Test.java


Uruchomienie

W celu uruchomienia programu, wywołaj javę i podaj argumenty. Upewnij się, że nasz program działa poprawnie i drukuje sumę liczb.

> java Test 3 4
7

C/Java Native Interface

Konfiguracja

Zainstaluj Chocolatey i przy jego użyciu zainstaluj narzędzia do budowania Visual C++. 

choco install visualcpp-build-tools


Będziemy ich potrzebowali do skompilowania plików C w bibliotekę dll file. Co więcej, będzie nam też do tego potrzebna komenda cl, sprawdźcie więc, czy ona działa.

>cl
Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27031.1 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

Kod

Java może się komunikować z C przez Java Native Interface. Aby skonfigurować kanał komunikacyjny musimy uaktualnić nasz program Javy: 

public class Test {
 
    public static void main(String[] args) {
  
        if (args.length != 2) {
            System.out.println("Error: wrong params count");
            return;
        }
  
        int a;
        try {
            a = Integer.parseInt(args[0]);
        } catch (Throwable throwable) {
            System.out.println("First param is not a number");
            return;
        }
  
        int b;
        try {
            b = Integer.parseInt(args[1]);
        } catch (Throwable throwable) {
            System.out.println("Second param is not a number");
            return;
        }
        System.loadLibrary("Test");
        Test test = new Test();
        System.out.println(test.sum(a, b));
    }
 
    public native int sum(int a, int b);
}


Po pierwsze, zamiast statycznej metody z implementacją, dostarczamy tak zwaną metodę natywną. Nie ma ona implementacji, ponieważ spodziewamy się, że zapewni ją JNI. Po drugie, musimy załadować naszą bibliotekę C, a zrobimy to dzięki metodzie System.loadLibrary.

Po aktualizacji naszego programu, musimy wygenerować plik nagłówkowy Test.h:

> javah Test


Wygenerowany plik nagłówkowy będzie zawierał wszystkie rzeczy potrzebne do skonfigurowania programu w C. Program w Javie zawierał jedną metodę natywną, a tutaj mamy deklarację dla naszej metody wygenerowanej przez plik nagłówkowy: 

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Test */
#ifndef _Included_Test
#define _Included_Test
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     Test
 * Method:    sum
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_Test_sum
  (JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif


Potrzebujemy implementacji naszej funkcji, stwórzmy zatem plik Test.c i zaimplementujmy metodę:

#include "Test.h"
JNIEXPORT jint JNICALL Java_Test_sum
  (JNIEnv *env, jobject obj, jint a, jint b) {
   return a + b;
  }
void main() {}

Kompilacja 

Kompilujemy naszą bibliotekę C w Test.dll:

> cl -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -LD Test.c -FeTest.dll

Uruchamianie

Przekompiluj nasz program Javy, jak pokazano wyżej, i uruchom go, żeby sprawdzić, czy działa:

> java Test 3 4
7

Assembly

Kod

Poprzednio mieliśmy implementację C, która wyglądała właściwie jak ta w Javie, czyli a + b. Podczas pracy w Assembly funkcjonujemy na niższym poziomie więc małe operacje wymagają większej ilości kodu.

Aktualizujemy nasz program C, aby móc używać Assembly. W tym celu dodajemy __asm block, który funkcjonuje jako Inline Assembler dla Visual C++. 

Wewnątrz bloku piszemy instrukcje. Zauważ, że umieszczamy nasze zmienne w rejestrze eax. Umieśćmy zatem naszą zmienną b w rejestrze ebx. Następnie sumujemy ze wszystkich rejestrów i przechowujemy w rejestrze eax (to właśnie robi komenda add).

I na koniec, umieszczamy wartość rejestru eax w naszym polu rezultatów:

#include "Test.h"
#include <stdio.h>
JNIEXPORT jint JNICALL Java_Test_sum
  (JNIEnv *env, jobject obj, jint a, jint b) {
    int result;
    __asm {
        mov eax, a
        mov ebx, b
        add eax, ebx
        mov result, eax
    }
    return result;
}
void main() {}


Skompiluj bibliotekę C ponownie (nie ma potrzeby, żeby robić to z programem Javy) i uruchom program Javy ponownie:

> java Test 3 4
7


Działa!

Mam nadzieję, że dobrze się bawiliście i czegoś się nauczyliście. Jeśli nie, to mam chociaż nadzieję, że to było śmieszne. 

Przyjemnego programowania!
Vasya Drobushkov 


Oryginał tekstu w języku angielskim przeczytasz tutaj.

<p>Loading...</p>