JNI不适用于__stdcall

JNI does not work with __stdcall

本文关键字:stdcall 适用于 不适用 JNI      更新时间:2023-10-16

我在Windows 7x64上玩JNI,Java版本为1.7.0_40,MinGW/GCC/G++4.7.2。

试图从Java关闭我的显示器。所以,我创建了一个类:

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();
}

然后,从中生成h文件(使用Eclipse,但我认为它使用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

并实现了它:

#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);
}

它不起作用!我得到例外:

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)

我被卡住了一段时间,但在谷歌搜索后,我找到了一个解决方案——使用__cdecl而不是__stdcall约定导出函数。所以,我做了一个非常肮脏的黑客:

#define JNICALL __cdecl

它起作用了!

它以前在jni_md.h被定义为__stdcall。我意识到我正在做一件非常糟糕的事情,这会伤害小猫,但我缺乏C/C++的经验,我不明白为什么我要重新定义它?为什么标准标头(jni_md.h)对此定义不正确?

对于jni使用的32位DLL,必须在没有通常的__stdcall装饰的情况下编译代码,即代码不能在符号名称上有@N后缀;但它仍然需要以CCD_ 9模式进行编译。

在mingw下编译dll时,需要将选项--kill-at添加到链接器中。这通常使用-Wl,--kill-at来传递。这将导致@N后缀被删除,因此看起来像一个简单的符号,可以在运行时由JVM链接。该选项也可以缩写为-Wl,-k

另一种选择是使用映射文件,该文件以未映射的形式导出符号,这是使用微软自己的visual studio编译器进行编译时最常用的机制。

这是一个非常糟糕的记录,当它发生时,你得到的错误并没有很好的帮助。

我建议查看JNA,它使编写本机代码包装器变得简单得多,在这种情况下,这意味着不需要C++代码。

实现这一点的JNA代码看起来像:

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();
}
};

即使你在Windows上,你也在使用GCC,这意味着:Linux中有STDCALL吗?基本上仍然适用。

java.h生成的文件中的函数声明为extern"C"您必须将其添加到cpp文件中:

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