Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsWhite Papers
Discussion GroupsFirst AidDatabasesJavaBeansGUIJava 3DVirtual MachineCORBASecurityToolsGeneral
Java DirectoryOpen Source ProjectsSample Book ChaptersUser GroupsWeb Resources
Related Topics
Databases.NETMore Topics ...

Java Forum / General / August 2006

Tip: Looking for answers? Try searching our database.

SetWindowsHookEx not notifing me on key pressed, using JNI and C++ dll

Thread view: 
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 Magazines

Get 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 ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.