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

K

Knitter

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

Jean-Francois Briere

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
 
K

Knitter

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

Knitter

Knitter said:
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:


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.

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"
));
}
 
J

Jean-Francois Briere

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
 
K

Knitter

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
 
J

Jean-Francois Briere

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?
 
K

Knitter

Jean-Francois Briere said:
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?

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

Jean-Francois Briere

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?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,070
Latest member
BiogenixGummies

Latest Threads

Top