Thursday, June 5, 2008

user32.dll Functions in JNI

UPDATE NOTE:
Please read this post explaining a small issue with the code used in this article.


If you've never used JNI before, please read my previous post first.

In this post I'm going to go over how to use some windows functions with JNI. Specifically, two which have caused me frustration recently: GetForegroundWindow and GetWindowText.

Reference Pages:
GetForegroundWindow
GetWindowText
GetWindowTextLength
GetLastError
C++ JNI Types/Functions

To the code! This is similar to my initial code - there is a problem with it. Hopefully you don't see it, so as to keep my programming ego intact. Again, this code DOES NOT WORK - if you're just here for the code look down further.

[javanative.cpp]

#include "windows.h"
#include "winuser.h"
#include "jni.h"
#include "javaclass.h" // generated header file

JNIEXPORT jstring JNICALL Java_javaclass_getForegroundWindow(JNIEnv *env, jobject obj){
HWND hwnd = GetForegroundWindow();
if(hwnd == 0){
return NULL;
}
int length = GetWindowLength(hwnd);
LPTSTR buffer = "xxxxxxxxxxxxxxxxxxxx"; // this is how I found a few examples online
int textLength = GetWindowText(hwnd, buffer, 20);
return env->NewStringUTF(buffer); // this too - memory leak. see code below
}

Note that the functions (GetWindowText, etc.) are in user32.dll; in order to use them, you must include the user32.lib library during compilation. The method to do this depends on your compiler; with the Visual Studio command-line compiler, you just append the libraries to the end (as you'll see below).

Compilation:

javac javaclass.java
javah -jni javaclass
call "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\vcvarsall.bat" amd64
cl -I"c:\program files\java\jdk1.6.0_06\include" -I"c:\program files\java\jdk1.6.0_06\include\win32" -I"." -MT -LD javanative.cpp -Fejavanative.dll user32.lib



Now then, it compiles - but it doesn't work. It returns the "xxxx..." string as the name every time. After some debugging, I found that GetWindowText was returning an error code of 1400 - and that code means that the window does not exist. Some more testing confirmed that the window DID exist - IsWindow returned true, etc. So, what's the problem?

As you probably guessed (the only commented line in the code):


LPTSTR buffer = "xxxxxxxxxxxxxxxxxxxx"; // this is how I found a few examples online


Apparently, having the buffer on the stack makes the call return 1400 - a completely wrong error code. The correct way to do it is to put the buffer on the heap.


int length = GetWindowTextLength(hwnd);
// make some heap space - lots of ways to do this.
LPTSTR buffer = (LPTSTR)malloc(length * sizeof(TCHAR));


It works!

Here's the full, working code:

#include "windows.h"
#include "winuser.h"
#include "jni.h"
#include "javaclass.h"

JNIEXPORT jstring JNICALL Java_nimbus_getForegroundWindow(JNIEnv *env, jobject obj){

HWND hwnd = GetForegroundWindow();
if(hwnd == 0){
return NULL;
}
int length = GetWindowTextLength(hwnd);
LPTSTR buffer = (LPTSTR)malloc(length * sizeof(TCHAR));
int textLength = GetWindowText(hwnd, buf, length);
DWORD lasterror = GetLastError();
jstring jstr = env->NewStringUTF(buffer);
free(buffer);
return jstr;
}
Note that even though we allocate heap space, we don't want to free it ourselves. This is not a memory leak, Java's GC will deallocate it when it's no longer in use.

And there you have it. Calling getForegroundWindow() in your java source now returns the caption of the foreground window.

1 comment:

ph3nix said...

Just commenting to thank you: THANK YOU!
Your post really helped me to solve my problem, I'm kind of stuck in C++ hWnd programming.
Best regards,
ph3nix