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.
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:
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)
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;
}
}
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
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
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.
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() {}
Kompilujemy naszą bibliotekę C w Test.dll
:
> cl -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -LD Test.c -FeTest.dll
Przekompiluj nasz program Javy, jak pokazano wyżej, i uruchom go, żeby sprawdzić, czy działa:
> java Test 3 4
7
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.
Oryginał tekstu w języku angielskim przeczytasz tutaj.