Java JNI and Windows messages

For a project I needed a simple application which starts on boot and runs in the background. When the program closes it writes it’s current state to disk.

In the development enviroment everything seemed to work. When the program closed, the state was written to disk using the Runtime.addShutdownHook(). When the project was ready for testing outside the IDE, I discovered the shutdownHook was not called when shutting down or restarting Windows.

After some Googling, I found out Windows sends a WM_ENDSESSION message to all programs with a Window. As fair as I could find out there’s no pure Java solution to retrieve and handle these messages. You can use SWT or JNA to do this, but I wanted to find out how to retrieve it by using JNI.

While Java JNI looks hard on first glance, it’s actually a quite easy. The hardest part was writing the C/C++ code - because I'm no C developer ;)

The example given is a simple Swing application, which uses some native code to listen to the messages. To retrieve Windows-messages there must be a (hidden) window created with a messageloop.


The class MylibJni contains a “public static native void” function. The JDK contains a tool ‘javah’, which generates a header file for the functions marked ‘native’. Marking a function to call native C is as simple as this. The header file can be generated with the following command. Make ofcourse sure the class is in your classpath.

javah com.myproject.MylibJni

package com.myproject;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;

public class MylibJni { public static native void listenMessages();
/**
* wrapper for listenMessages, to load the .dll-file & call listenMessage() in
* a thread. It's necessary to run it in a Thread, because the DLL creates a Window
* and enters a message loop
*
*/
public static void listenMessagesWrapper() {
System.load("C:/projects/jniblog/workspace/mylib/Debug/libmylib.dll");

Thread t = new Thread(new Runnable() {
public void run() {
listenMessages();
}
});

t.start();
}


/**
* called from mylib.dll when WM_ENDSESSION is received
*
*/
public static void wmEndsession() throws IOException {
File f = new File("C:/tmp/WmEndsession.txt");
f.createNewFile();

PrintWriter pw = new PrintWriter(f);
pw.println("WmEndsession called! - " + new Date());
pw.close();

}

}



The generated headerfile for the class com.myproject.MylibJni looks as follow,

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_myproject_MylibJni */ #ifndef _Included_com_myproject_MylibJni
#define _Included_com_myproject_MylibJni
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_myproject_MylibJni
* Method: listenMessages
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_myproject_MylibJni_listenMessages
(JNIEnv *, jclass); #ifdef __cplusplus
}
#endif
#endif



Next create a file, for example mylib.c, for the implementation. This sourcefile contains the entrypoint when com.myproject.MylibJni.listenMessages() is called. What it does is create a hidden window and enters a message loop. Because it enters a loop it will be started in a separated thread, else the program would hang. In this example the thread is created in Java – listenMessagesWrapper – because that’s easier then creating it in C

The function registerWndProc creates the hidden window.

The function WndProc handles received messages.

The function calljvm is called when the WM_ENDSESSION is received.


#include "com_myproject_MylibJni.h"
#include <stdio.h>
#include <windows.h>
#include <signal.h>
JNIEnv *cur_env; /* pointer to native method interface */
void calljvm() {
JavaVM *jvm; (*cur_env)->GetJavaVM(cur_env, &jvm);
(*jvm)->AttachCurrentThread(jvm, (void **)(&cur_env), &cur_env);
jclass cls = (*cur_env)->FindClass(cur_env, "com/myproject/MylibJni");
jmethodID mid = (*cur_env)->GetStaticMethodID(cur_env, cls, "wmEndsession", "()V");
(*cur_env)->CallStaticVoidMethod(cur_env, cls, mid); (*jvm)->DetachCurrentThread(jvm);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam,LPARAM lParam)
{
switch(msg) {
case WM_ENDSESSION :
calljvm();
break;
default:
break;
} return DefWindowProc(hwnd, msg, wParam, lParam); }
void registerWndProc() {
HINSTANCE h2 = GetModuleHandle(NULL); static char szAppName[] = "winhello"; WNDCLASSEX wndclass; wndclass.cbSize = sizeof(wndclass);
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = h2;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
wndclass.lpszClassName = szAppName;
wndclass.lpszMenuName = NULL;
/* Register a new window class with Windows */
if (!RegisterClassEx(&wndclass)) {
MessageBox(NULL, TEXT("Registering class failed!"), TEXT("Error!"), MB_ICONEXCLAMATION | MB_OK);
return;
}
/* Create a window based on our new class */
HWND hwnd; hwnd = CreateWindow(szAppName, "Hello, world!",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, h2, NULL);
/* CreateWindow failed? */
if( !hwnd ) {
MessageBox(NULL, TEXT("Window creation failed!"), TEXT("Error!"), MB_ICONEXCLAMATION | MB_OK);
return;
}
/* Show and update our window */
ShowWindow(hwnd, 0); /* 0 = SW_HIDE */
UpdateWindow(hwnd); MSG msg;
while (GetMessage(&msg, NULL, 0, 0) ) {
TranslateMessage(&msg); /* for certain keyboard messages */
DispatchMessage(&msg); /* send message to WndProc */
}
}
JNIEXPORT void JNICALL Java_com_myproject_MylibJni_listenMessages(JNIEnv *env, jclass clazz) {
cur_env = env; /* Creates window & enters message loop */
registerWndProc();
}

 

Beware that the JDK has a 32-bits and 64-bits version. The dll file must be compiled for the right architecture. In my project I used MinGW to compile the dll, but it’s probably easier to use Visual Studio.

Click here to download the example project



Gerelateerd

07-07-2016 WMI scripting example
13-06-2016 Introduction to Angular 2, a hello world example
- Bent u opzoek naar een Php of Java programmeur voor uw website of applicatie? (freelance / detachering)
- Losse tickets, opdrachten, of gehele projecten in de planning?

Dan kom ik graag met u in contact! Meer informatie over mij vindt u hier.
Sitemap | Op alle producten & diensten zijn de algemene voorwaarden van toepassing