JNI does not work with __stdcall

2019-07-27 04:21发布

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?

3条回答
2楼-- · 2019-07-27 04:46

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:

import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinUser;
import com.sun.jna.platform.win32.WinDef.LPARAM;
import com.sun.jna.platform.win32.WinDef.WPARAM;

public class JNAMonitorTrigger {
    public void monitor(LPARAM param) {
        User32.INSTANCE.PostMessage(WinUser.HWND_BROADCAST,
            WinUser.WM_SYSCOMMAND,
            new WPARAM(0xf170), param);
    }
    public void on() {
        monitor(new LPARAM(-1));
    }

    public void off() {
        monitor(new LPARAM(2));
    }

    public static void main(String args[]) throws Exception {
        JNAMonitorTrigger me = new JNAMonitorTrigger();
        me.off();
        Thread.sleep(1000);
        me.on();
    }
};
查看更多
甜甜的少女心
3楼-- · 2019-07-27 04:54

Even though you're on Windows, you're using GCC, which means this: Is there STDCALL in Linux? is still basically going to apply.

查看更多
成全新的幸福
4楼-- · 2019-07-27 04:57

The functions in your file generated by java.h are declared as extern "C" You have to add it in your cpp file :

extern "C"
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);
}
查看更多
登录 后发表回答