Java Forum / General / August 2006
SetWindowsHookEx not notifing me on key pressed, using JNI and C++ dll
Knitter - 27 Jul 2006 20:11 GMT I'm trying to develop a simple program that intercepts braille key strokes and translates them into normal letters. The goal is to create a "braille keyboard emulator", is one can call it that. I'm using java and a c++ dll making them comunicate with JNI. The java program loads the dll in a static block in the Main class with the 'System.loadLibrary' method the dll is loaded and the 'DllMain' funtion is called. What I don't know is if the 'SetWindowsHookEx' is called or not. I can make the java program send messages to the C++ dll library and I can make the dll send back messages to the java program but I'm never notified about keys being pressed. I have no idea about what is going wrong and have had no success searching for similar problems on the web.
Here is the code for the C++ dll:
/** Library that allows the hooking of the keyboard so that the Java componet of the system is able to process the key codes and send the right information to the user desktop aplication. It's responsible for the 'grabing' of the keyboard and is the bridge between the java part and the aplication waiting for the key. */
#include "winlib.h" #include "bkey_keyParser.h" #include "resource.h" #include <windows.h> #include <stdio.h> #include <stdlib.h>
//DllClass::DllClass(){} //DllClass::~DllClass (){}
LRESULT CALLBACK LowLevelKeyboardProc(int, WPARAM, LPARAM); jobject parser; JNIEnv *java; HHOOK hhkLowLevelKybd = NULL;
/*'Params' HINSTANCE hInst - Library instance handle. DWORD reason - Reason this function is being called. LPVOID reserved - Not used. */ extern "C" BOOL APIENTRY DllMain (HINSTANCE hInst, DWORD reason, LPVOID reserved) {
printf("Metodo dllmain invocado\n"); switch (reason) { case DLL_PROCESS_ATTACH: //start hook! hhkLowLevelKybd = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, hInst, 0); if(hhkLowLevelKybd == NULL) printf("hook at process attach failed!"); else printf("hook feito no process attach\n"); break; case DLL_PROCESS_DETACH: //break hook! UnhookWindowsHookEx(hhkLowLevelKybd); printf("unhook feito no process detach\n"); break;
/*Are process and thread called allways or is only one of them called?*/ case DLL_THREAD_ATTACH: //ignore printf("DLL_THREAD_ATTACH invocado\n"); break; case DLL_THREAD_DETACH: //ignore printf("DLL_THREAD_DETACH invocado\n"); break; }
/* Returns TRUE on success, FALSE on failure */ return TRUE; }
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { printf("callback de teclado invocada!\n"); KBDLLHOOKSTRUCT *p = (KBDLLHOOKSTRUCT *) lParam; switch (wParam) { case WM_KEYDOWN: case WM_SYSKEYDOWN: { printf("Pressionada a tecla\n"); jclass cls = (java)->GetObjectClass(parser); jmethodID mid = (java)->GetMethodID(cls, "processKey", "(I)V"); (java)->CallVoidMethod(parser, mid, p->vkCode); } break; case WM_KEYUP: case WM_SYSKEYUP: printf("Tecla libertada\n"); break; }
//return the control to the sytem! return CallNextHookEx(NULL, nCode, wParam, lParam); }
/* Function called by the Java part in order to return the processed key. */ JNIEXPORT void JNICALL Java_bkey_KeyParser_parsedKey (JNIEnv *env, jobject object, jint key) { if(key == VK_L) printf("Java enviou a tecla L\n"); else printf("Codigo da tecla enviada por Java diferente da tecla L\n"); }
/* Function called by the Java part to register itself as the owner and caller of this library. This is used to make the library able to call java methods and send the intercepted keys to be processed. */ JNIEXPORT void JNICALL Java_bkey_KeyParser_registerOwner (JNIEnv *env, jobject object) { printf("C a registar o dono\n"); parser = object; java = env; }
Thanks.
Jean-Francois Briere - 27 Jul 2006 23:17 GMT Here are the modifications to make:
LRESULT CALLBACK LowLevelKeyboardProc(int, WPARAM, LPARAM); jobject parser; JavaVM *javaVM; // never use JNIEnv * as global HHOOK hhkLowLevelKybd = NULL;
...
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { JNIEnv *java; // retrieve JNIEnv * from JavaVM * if (javaVM->AttachCurrentThread((void **)&java, NULL) < 0) { // some error handling }
...
JNIEXPORT void JNICALL Java_bkey_KeyParser_registerOwner (JNIEnv *env, jobject object) { printf("C a registar o dono\n"); parser = env->NewGlobalRef(object); // be sure that parser won't be garbage collected env->GetJavaVM(&javaVM); // retrieve JavaVM * as global }
Regards
Knitter - 28 Jul 2006 22:22 GMT Some points I don't understand... Why can't I use JNIEnv as global? It's in some of the examples I have. Why do I need to attach the dll to the VM? I was under the impression that when a javaVM loads a dll the dll is already attached to the VM, I'm I wrong?
I'm using DevC++ and Eclipse, and I have my classes and my dll in the same folder, <project folder>\bin, and I run the app from a DOS command shell like this: java -Djava.library.path=bin -classpath bin bkey.Main
The output is:
>Testing: Dll main method called. >hook successful at process attach >Testing: Registering dll owner. >Testing: JavaVM not null: True. Then when I press a key the cursor just stops blinking for 2 or 3 seconds and nothing else happens, if I press any other keys nothing happens.
Is there any where I can see some correct documentation? I find alot of contradictory info over the net, and I can't decide what is usefull and is just old or garbage.
Thanks.
Knitter - 28 Jul 2006 22:27 GMT > Some points I don't understand... > Why can't I use JNIEnv as global? It's in some of the examples I have. [quoted text clipped - 23 lines] > > Thanks. Sorry I forgot to put in the C++ code. Here it is:
#include "winlib.h" #include "bkey_keyParser.h" #include "resource.h" #include <windows.h> #include <stdio.h> #include <stdlib.h>
LRESULT CALLBACK LowLevelKeyboardProc(int, WPARAM, LPARAM); jobject parser; JavaVM *javaVM = NULL; HHOOK hhkLowLevelKybd = NULL;
/*'Params' HINSTANCE hInst - Library instance handle. DWORD reason - Reason this function is being called. LPVOID reserved - Not used. */ extern "C" BOOL APIENTRY DllMain (HINSTANCE hInst, DWORD reason, LPVOID reserved) {
printf("Testing: Dll main method called.\n"); switch (reason) { case DLL_PROCESS_ATTACH: //start hook! hhkLowLevelKybd = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, hInst, 0); if(hhkLowLevelKybd == NULL) printf("hook at process attach failed!\n"); else printf("hook successful at process attach\n"); break; case DLL_PROCESS_DETACH: //break hook! UnhookWindowsHookEx(hhkLowLevelKybd); printf("Final: Unhook successful at process detach\n"); break;
/*Are process and thread called allways or is only one of them called?*/ case DLL_THREAD_ATTACH: //ignore printf("DLL_THREAD_ATTACH ignored\n"); break; case DLL_THREAD_DETACH: //ignore printf("DLL_THREAD_DETACH ignored\n"); break; }
/* Returns TRUE on success, FALSE on failure */ return TRUE; }
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { JNIEnv *java; KBDLLHOOKSTRUCT *p = (KBDLLHOOKSTRUCT *) lParam; printf("Testin: Low Level Proc calld.\n"); if(javaVM->AttachCurrentThread((void **)&java, NULL) >= 0) { switch (wParam) { case WM_KEYDOWN: case WM_SYSKEYDOWN: { printf("Testing: Key pressed\n"); jclass cls = (java)->GetObjectClass(parser); jmethodID mid = (java)->GetMethodID(cls, "processKey", "(I)V"); (java)->CallVoidMethod(parser, mid, p->vkCode); } break; case WM_KEYUP: case WM_SYSKEYUP: printf("Testing: Key released\n"); break; } } else printf("Error: Error on the attach current thread thing!\n");
//return the control to the sytem! return CallNextHookEx(NULL, nCode, wParam, lParam); }
/* Function called by the Java part in order to return the processed key. */ JNIEXPORT void JNICALL Java_bkey_KeyParser_parsedKey (JNIEnv *env, jobject object, jint key) { printf("Testing: Java successfuly sent key code to the dll.\n"); }
/* Function called by the Java part to register itself as the owner and caller of this library. This is used to make the library able to call java methods and send the intercepted keys to be processed. */ JNIEXPORT void JNICALL Java_bkey_KeyParser_registerOwner (JNIEnv *env, jobject object) { printf("Testing: Registering dll owner.\n"); parser = env->NewGlobalRef(object); env->GetJavaVM(&javaVM); printf("Testing: JavaVM not null: %s\n", (javaVM ? "True" : "False" )); }
Jean-Francois Briere - 01 Aug 2006 23:15 GMT Here is a very simple yet complete working sample of a low level Windows keyboard hook within a Swing application. Please try it.
// // FrameTest.java // import java.awt.*; import java.awt.event.*; import javax.swing.*;
public class FrameTest extends JFrame { private JPanel mainPanel; private JTextArea mainTextArea; private HookTest hook;
public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { new FrameTest().setVisible(true); } }); }
FrameTest() { super("FrameTest"); setSize(200, 200); setDefaultCloseOperation(EXIT_ON_CLOSE); mainPanel = new JPanel(); mainPanel.setLayout(new BorderLayout()); mainTextArea = new JTextArea(); mainPanel.add(mainTextArea, BorderLayout.CENTER); getContentPane().add(mainPanel); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent event) { hook.unRegisterHook(); } }); new Thread() { public void run() { hook = new HookTest(); hook.registerHook(); } }.start(); } }
// // HookTest.java // public class HookTest { static { System.loadLibrary("HookTest"); }
void processKey(int key, boolean pressed) { System.out.println("Java: HookTest.processKey - key = " + key + (pressed ? " pressed" : " released")); }
native void registerHook(); native void unRegisterHook(); }
// // HookTest.h // #ifndef _Included_HookTest #define _Included_HookTest
#include <jni.h>
#ifdef __cplusplus extern "C" { #endif
JNIEXPORT void JNICALL Java_HookTest_registerHook(JNIEnv * env, jobject obj);
JNIEXPORT void JNICALL Java_HookTest_unRegisterHook(JNIEnv * env, jobject obj);
#ifdef __cplusplus } #endif
#endif /* _Included_HookTest */
// // HookTest.cpp // #include <windows.h> #include "HookTest.h"
HINSTANCE hInst = NULL; JavaVM * jvm = NULL; jobject hookObj = NULL; jmethodID processKeyID = NULL; DWORD hookThreadId = 0;
extern "C" BOOL APIENTRY DllMain(HINSTANCE _hInst, DWORD reason, LPVOID reserved) { switch (reason) { case DLL_PROCESS_ATTACH: printf("C++: DllMain - DLL_PROCESS_ATTACH.\n"); hInst = _hInst; break; default: break; }
return TRUE; }
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { JNIEnv * env; KBDLLHOOKSTRUCT * p = (KBDLLHOOKSTRUCT *)lParam;
if (jvm->AttachCurrentThread((void **)&env, NULL) >= 0) { switch (wParam) { case WM_KEYDOWN: case WM_SYSKEYDOWN: printf("C++: LowLevelKeyboardProc - Key pressed\n"); env->CallVoidMethod(hookObj, processKeyID, p->vkCode, true); break; case WM_KEYUP: case WM_SYSKEYUP: printf("C++: LowLevelKeyboardProc - Key released\n"); env->CallVoidMethod(hookObj, processKeyID, p->vkCode, false); break; default: break; } } else { printf("C++: LowLevelKeyboardProc - Error on the attach current thread.\n"); }
return CallNextHookEx(NULL, nCode, wParam, lParam); }
void MsgLoop() { MSG message;
while (GetMessage(&message, NULL, 0, 0)) { TranslateMessage(&message); DispatchMessage(&message); } }
JNIEXPORT void JNICALL Java_HookTest_registerHook(JNIEnv * env, jobject obj) { HHOOK hookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, hInst, 0);
if (hookHandle == NULL) { printf("C++: Java_HookTest_registerHook - Hook failed!\n"); return; } else { printf("C++: Java_HookTest_registerHook - Hook successful\n"); }
hookObj = env->NewGlobalRef(obj); jclass cls = env->GetObjectClass(hookObj); processKeyID = env->GetMethodID(cls, "processKey", "(IZ)V"); env->GetJavaVM(&jvm); hookThreadId = GetCurrentThreadId();
MsgLoop();
if (!UnhookWindowsHookEx(hookHandle)) printf("C++: Java_HookTest_registerHook - Unhook failed\n");
else printf("C++: Java_HookTest_registerHook - Unhook successful\n"); }
JNIEXPORT void JNICALL Java_HookTest_unRegisterHook(JNIEnv *env, jobject object) { if (hookThreadId == 0) return;
printf("C++: Java_HookTest_unRegisterHook - call PostThreadMessage.\n"); PostThreadMessage(hookThreadId, WM_QUIT, 0, 0L); }
Regards
Knitter - 03 Aug 2006 01:39 GMT Thanks for the code but... I know have a bit of a problem making it run
:) I have a "java.lang.UnsatisfiedLinkError" exception. I know what the exception is but somehow I can't solve the problem. I know its related to my path so I tryed to run your code the same way I was running mine. With: java -Djava.library.path=bin -classpath bin hook.FrameTest where "bin" is the folder where the java's classes are located along with the HookTest.dll
Now what am I doing wrong? or what am I not doing? I have not tryed to set my path to this folder, and at this late hour I'm not going to, but if I use the java.library.path option shouldn't it find the lib?
Thanks
Jean-Francois Briere - 03 Aug 2006 02:26 GMT Maybe you should try with absolute path instead of relative ones:
java -Djava.library.path=c:\some\folder\bin -classpath c:\some\folder\bin hook.FrameTest
Also as I understand you've added the line: package hook; on top of the 2 java classes I posted.
1- Are the 2 compiled .class files located under sub-folder hook in your bin folder? c:\some\folder\bin\hook\HookTest.class c:\some\folder\bin\hook\FrameTest.class 2- Did you make other changes to the sources I posted? 3- Do you have HookTest.dll located under your bin folder? c:\some\folder\bin\HookTest.dll 4- What exact UnsatisfiedLinkError do you have? Please post the exact first 3 lines of the stack trace. 5- Which compiler are you using to build HookTest.dll? Can you tell what are the compile flags used?
Knitter - 03 Aug 2006 08:56 GMT > Maybe you should try with absolute path instead of relative ones: > [quoted text clipped - 16 lines] > 5- Which compiler are you using to build HookTest.dll? > Can you tell what are the compile flags used? Problem solved! I studied your code and even if there are a few things I don't understand why you made it I'm on my way. I tryed the absolute path, no go. 1 - yes they are - no go 2 - no I made no other changes 3 - yes, the dll is in the bin folder - no go 4 - sorry can't duplicate the error again. 5- i'm using mingw port for windows of gcc with the Bloodshed DevC++ IDE 4.9.9.2 Don't have the project here so I can't say what are the flags, but I know I didn't change them from installation so they are default. And after I include your code in a package I recreated the .h file with the javah. It created a file equal to yours but with the name that reflected the full class name. Other then that it was the same
I manage to make the code work by making changes in my code based on what you gave me. I didn't actualy solve the problem just got around it
:) Well I''ve been on this problem for a month so thank you for all the help.
Jean-Francois Briere - 03 Aug 2006 09:13 GMT > I studied your code and even if there are a few things I don't > understand why you made it I'm on my way. Maybe I could try to explain more about the grey areas. Which parts of my code are unclear?
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|