I'm playing around with JNI on Windows 7x64, Java version is 1.7.0_40 and MinGW / GCC / G++ 4.7.2.
Trying to power off my monitor from Java. So, I've created a class:
public class MonitorTrigger {
static {
try {
System.load(new ClassPathResource("MonitorTrigger.dll").getFile().getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
}
public native void on();
public native void off();
}
Then, generated h
file from it (with Eclipse, but I beleive it uses javah
):
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class by_dev_madhead_bubikopf_desktop_monitortrigger_util_MonitorTrigger */
#ifndef _Included_by_dev_madhead_bubikopf_desktop_monitortrigger_util_MonitorTrigger
#define _Included_by_dev_madhead_bubikopf_desktop_monitortrigger_util_MonitorTrigger
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: by_dev_madhead_bubikopf_desktop_monitortrigger_util_MonitorTrigger
* Method: on
* Signature: ()V
*/
JNIEXPORT void
JNICALL Java_by_dev_madhead_bubikopf_desktop_monitortrigger_util_MonitorTrigger_on(
JNIEnv *, jobject);
/*
* Class: by_dev_madhead_bubikopf_desktop_monitortrigger_util_MonitorTrigger
* Method: off
* Signature: ()V
*/
JNIEXPORT void
JNICALL Java_by_dev_madhead_bubikopf_desktop_monitortrigger_util_MonitorTrigger_off(
JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
And implemented it:
#include "by_dev_madhead_bubikopf_desktop_monitortrigger_util_MonitorTrigger.h"
#include <iostream>
#include <windows.h>
JNIEXPORT void JNICALL Java_by_dev_madhead_bubikopf_desktop_monitortrigger_util_MonitorTrigger_on
(JNIEnv * env, jobject object) {
SendMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER, (LPARAM) -1);
}
JNIEXPORT void JNICALL Java_by_dev_madhead_bubikopf_desktop_monitortrigger_util_MonitorTrigger_off
(JNIEnv * env, jobject object) {
SendMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER, (LPARAM) 2);
}
It does not work! I get exception:
Exception in thread "main" java.lang.UnsatisfiedLinkError: by.dev.madhead.bubikopf.desktop.monitortrigger.util.MonitorTrigger.off()V
at by.dev.madhead.bubikopf.desktop.monitortrigger.util.MonitorTrigger.off(Native Method)
at by.dev.madhead.bubikopf.desktop.monitortrigger.App.main(App.java:7)
I was stuck for a bit, but after googling, I've found a solution - use __cdecl
instead of __stdcall
convention for exported function. So, I've made a very dirty hack:
#define JNICALL __cdecl
And it works!
It was previously defined as __stdcall at jni_md.h
. I realize that I'm doing a very bad thing, which harm kittens, but I lack of experience in C/ C++, and I cannot figure out, why I should redefine this? Why standard header (jni_md.h
) defines this incorrectly?
For 32 bit DLLs being used by jni, the code has to be compiled without the usual
__stdcall
adornments - i.e. the code can't have the@N
postfix on the symbol name; but it still needs to be compiled in__stdcall
mode.When compiling the dll under mingw, you need to add the option
--kill-at
to the linker. This is generally passed using-Wl,--kill-at
. This will cause the@N
postfix to be removed, and so appear as a simple symbol which can be linked at run-time by the JVM. The option can be abbreviated to-Wl,-k
as well.An alternative is to use a map file, which exports the symbols in their unmangled form, which is the mechanism that's used most often when compiling with microsoft's own visual studio compiler.
This is pretty poorly documented, and the resulting error you get when it happens doesn't help too well.
I'd recommend looking at
JNA
, which makes writing native code wrappers a lot simpler, and in this case would mean no C++ code needed.The JNA code to accomplish this looks like:
Even though you're on Windows, you're using GCC, which means this: Is there STDCALL in Linux? is still basically going to apply.
The functions in your file generated by
java.h
are declared as extern "C" You have to add it in yourcpp
file :