JNI: kein Zugriff auf VM in Callback-Methode eines Windows-Hooks

ThomasE

Neues Mitglied
Hallo

Ich versuche gerade Windows beizubringen, dass es bei jedem Tastendruck, egal ob meine Anwendung den Fokus hat oder nicht, eine Methode meines Java-Programms aufruft. Um dies zu realisieren, ruft meine Java-Anwendung beim Start die Methode
Code:
Java_autopilot_KeyboardHook_start
in meiner selbstgeschriebenen DLL (der C-Code befindet sich ein Stück weiter unten) auf, die die Methode
Code:
KeyboardProc
als Keyboard-Callback registriert. Soweit funktioniert alles und die
Code:
KeyboardProc
wird auch bei jedem Tastendruck aufgerufen. Mein Problem ist, dass ich in
Code:
KeyboardProc
nicht auf die JVM zugreifen kann. Jeder Aufruf über den javaVm-Pointer schlägt fehlt und auch
Code:
JNI_GetCreatedJavaVMs
gibt 0 gefundene VMs (nVMs) zurück :-(

Ermittelt man in der Methode "Java_autopilot_KeyboardHook_start" mit JNI_GetCreatedJavaVMs(...) die verfügbaren VMs, dann bekommt man die eine existierende zurückgegeben.

Habt Ihr eine Idee, wieso die VM für die Keyboard-Callback-Methode nicht existiert ?

Danke

Thomas


Code:
#pragma data_seg("myJVM")

JavaVM* javaVm = NULL;

#pragma data_seg()

#pragma comment(linker,"/SECTION:myJVM,RWS")


JNIEXPORT jint JNICALL JNI_OnLoad( JavaVM *vm, void *pvt ) {

   javaVm = vm;

   printf("JNI_OnLoad(): javaVm = %X\n", javaVm);

   return JNI_VERSION_1_2;
}


JNIEXPORT void JNICALL Java_autopilot_KeyboardHook_start(JNIEnv* env, jobject obj) {

   HHOOK hook;

   hook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, dllInst, 0);

   if (hook == NULL) {

      printf("hookErr = %d\n", GetLastError());

   } else {

      Sleep(5000);

      UnhookWindowsHookEx(hook);
   }
}

extern "C" __declspec (dllexport) LRESULT CALLBACK KeyboardProc(int code, WPARAM wParam, LPARAM lParam) {

   FILE* file;
   char* buffer = new char[255];
   jint result = 4711;
   JNIEnv* env;
   jsize nVMs = 4711;
   JavaVM * vmBuf;


   fopen_s(&file, "C://hook.txt", "a");

   sprintf_s(buffer, 255, "w = %10X l = %10X vm = %X\n", wParam, lParam, javaVm);

   fputs(buffer, file);

   result = JNI_GetCreatedJavaVMs(&vmBuf, 1, &nVMs);

   sprintf_s(buffer, 255, "JNI_GetCreatedJavaVMs: result = %X nVMs = %X\n", result, nVMs);

   fputs(buffer, file);

   result = javaVm->AttachCurrentThreadAsDaemon((void**)&env, NULL);

   sprintf_s(buffer, 255, "AttachCurrentThreadAsDaemon: result = %d\n", result);

   fputs(buffer, file);

   fclose(file);

   return CallNextHookEx(NULL, code, wParam, lParam);
}
 
das problem könnte sein das du versuchst den pointer auf die VM im globalen kontext der DLL abzulegen
Code:
#pragma data_seg("myJVM")
JavaVM* javaVm = NULL;
#pragma data_seg()
#pragma comment(linker,"/SECTION:myJVM,RWS")
ganz davon abgesehen das es bei mehrfach-instanzzierung logisch immer nur die letzte VM hätte ist das aber so leider nicht möglich ... gründe dafür finden sich in der JNI spezifikation ...
nimm also den VM-pointer einfach aus dem global-kontext raus (also entferne einfach die pragma-zeilen) und es sollte funktionieren ...

alternativ hier ein link zu nem global-hook : Java – Global (low level) Keyboard / Mouse Hook – JNI kSquared.de – Blog
 
Beim Überfliegen sehe ich nirgendwo ein "getEnv" ... Siehe auch http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/invocation.html#GetEnv

Ein Stück Code, ohne Gewähr...
Code:
    jint attached = globalJvm->GetEnv((void**)&env, JNI_VERSION_1_4);
    if (attached != JNI_OK)
    {
        globalJvm->AttachCurrentThread((void**)&env, NULL);
    }

    env->CallVoidMethod(...);

    if (attached != JNI_OK)
    {
        globalJvm->DetachCurrentThread();
    }
 
Danke für Eure Antworten. Die geposteten URLs haben mich in die richtige Richtung gelenkt.

Der Grund warum es nicht funktioniert hat lag im Aufruf der Methode
Code:
SetWindowsHookEx
. In meiner ursprünglichen Version sah der Aufruf so aus:

Code:
SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, dllInst, 0);

Richtig ist aber ...

Code:
SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, dllInst, 0);

Übergibt man als erstes Argument WH_KEYBOARD, dann wird die Callback-Methode (KeyboardProc) von einem anderen Thread aus aufgerufen und von da aus hat man keinen Zugriff auf die bereits existierende VM.

Übergibt man als erstes Argument WH_KEYBOARD_LL, dann schickt Windows bei jedem Tastendruck an den Thread, in dem mit
Code:
SetWindowsHookEx
die Callback-Methode registriert wurde, eine Nachricht (z.B. WM_KEYDOWN). Somit muss man in diesem Thread auch eine Eventqueue einbauen, damit man diese Nachrichten verarbeiten kann. Da jetzt die Callback-Methode vom dem Thread ausgeführt wird, der auch die Callback-Methode bei Windows registriert hat, hat man auch Zugriff auf die VM.

Den funktionierenden Code habe ich Euch hier noch mal gepostet. Vielleicht hilft es ja irgendjemanden in Zukunft.

Code:
JavaVM*		javaVm		= NULL;
HHOOK		hook		= NULL;
jclass		javaClass;
jobject		javaObject;
jmethodID	methodId;
DWORD		hookThreadId	= NULL;

JNIEXPORT void JNICALL Java_autopilot_KeyboardHook_start(JNIEnv* env, jobject obj) {
	       
	MSG message;
	
	env->GetJavaVM(&javaVm);

	javaObject		= obj;
	javaClass		= env->GetObjectClass(obj);
	methodId		= env->GetMethodID(javaClass, "keyEvent", "(III)V");
	hookThreadId		= GetCurrentThreadId();

	if (methodId != NULL) {

		hook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, dllInst, 0); 
	
		if (hook == NULL) {
		
			printf("hookErr = %d\n", GetLastError());

		} else {

			while(GetMessage(&message, NULL, 0, 0)) {
				DispatchMessage(&message);
			}
		}
	}
}

JNIEXPORT void JNICALL Java_autopilot_KeyboardHook_stop(JNIEnv* env, jobject obj) {

	if (hook != NULL) {
		
		PostThreadMessage(hookThreadId,WM_QUIT,0,0L);
		hook = NULL;
		printf("unhooked the callback method\n");
	}
}

extern "C" __declspec (dllexport) LRESULT CALLBACK KeyboardProc(int code, WPARAM wParam, LPARAM lParam) {
	
	jint			result;
	JNIEnv*			env;
	KBDLLHOOKSTRUCT*	data;

	result = javaVm->AttachCurrentThread((void **)&env, NULL);
	
	if (result == JNI_OK) {
		
		data = (KBDLLHOOKSTRUCT*)lParam;
		env->CallVoidMethod(javaObject, methodId, allowedKeys[keyCount-1], data->vkCode, data->scanCode);
	}

	return CallNextHookEx(NULL, code, wParam, lParam);
}
 

Zurück
Oben