【发布时间】:2014-01-27 20:26:28
【问题描述】:
我有一个 Java 库(通过 JNA)与本机 C++ DLL 接口。此 DLL 提供函数来设置在某些硬件事件发生时调用的回调。
除了一个之外,所有这些回调都有效,即使它与另一个具有几乎相同的定义。
这些回调签名和枚举是在 C++ 代码中定义的:
typedef enum _KEYSTATE
{
KEYSTATE_NONE = 0,
KEYSTATE_UP,
KEYSTATE_DOWN,
KEYSTATE_HOLD,
KEYSTATE_INVALID,
} KEYSTATETYPE, *P_KEYSTATETYPE;
typedef enum _DKTYPE
{
DK_NONE = 0,
DK_1,
DK_2,
DK_3,
DK_4,
DK_5,
DK_6,
DK_7,
DK_8,
DK_9,
DK_10,
DK_INVALID,
DK_COUNT = 10
} DKTYPE, *P_DKTYPE;
typedef enum _GESTURETYPE
{
GESTURE_NONE = 0x00000000,
GESTURE_PRESS = 0x00000001,
GESTURE_TAP = 0x00000002,
GESTURE_FLICK = 0x00000004,
GESTURE_ZOOM = 0x00000008,
GESTURE_ROTATE = 0x00000010,
GESTURE_MOVE = 0x00000020,
GESTURE_HOLD = 0x00000040,
GESTURE_RELEASE = 0x00000080,
GESTURE_SCROLL = 0x00000100,
GESTURE_ALL = 0xFFFF
} GESTURETYPE, *P_GESTURETYPE;
typedef enum _EVENTTYPE
{
EVENT_NONE = 0,
EVENT_ACTIVATED,
EVENT_DEACTIVATED,
EVENT_CLOSE,
EVENT_EXIT,
EVENT_INVALID,
} EVENTTYPETYPE, *P_EVENTTYPETYPE;
typedef HRESULT (CALLBACK *DynamicKeyCallbackFunctionType)(DKTYPE, KEYSTATETYPE);
typedef HRESULT (CALLBACK *AppEventCallbackType) (EVENTTYPETYPE, DWORD, DWORD);
typedef HRESULT (CALLBACK *TouchpadGestureCallbackFunctionType)(GESTURETYPE, DWORD, WORD, WORD, WORD);
typedef HRESULT (CALLBACK *KeyboardCallbackFunctionType)(UINT uMsg, WPARAM wParam, LPARAM lParam);
Java 中为回调定义了以下接口:
interface DynamicKeyCallbackFunction extends StdCallLibrary.StdCallCallback {
int callback(int rawDynamicKeyType, int rawDynamicKeyState);
}
interface AppEventCallbackFunction extends StdCallLibrary.StdCallCallback {
int callback(int appEventType, WinDef.UINT dwAppMode, WinDef.UINT dwProcessID);
}
interface TouchpadGestureCallbackFunction extends StdCallLibrary.StdCallCallback {
int callback(int gestureType, WinDef.UINT dwParameters,
WinDef.USHORT wXPos, WinDef.USHORT wYPos, WinDef.USHORT wZPos);
}
interface KeyboardCallbackFunction extends StdCallLibrary.StdCallCallback {
int callback(WinDef.UINT uMsg, WinDef.UINT_PTR wParam, WinDef.INT_PTR lParam);
}
在 API/Library 类/接口中使用这些函数来设置它们:
// These are defined in an RazerAPI.java file as wrappers to simplify usage
// lib is an instance of a RazerLibrary from JNA
public Hresult RzSBAppEventSetCallback(AppEventCallbackFunction callback) {
return Hresult.getFromApiValue(lib.RzSBAppEventSetCallback(callback));
}
public Hresult RzSBDynamicKeySetCallback(DynamicKeyCallbackFunction callback) {
return Hresult.getFromApiValue(lib.RzSBDynamicKeySetCallback(callback));
}
public Hresult RzSBKeyboardCaptureSetCallback(KeyboardCallbackFunction callback) {
return Hresult.getFromApiValue(lib.RzSBKeyboardCaptureSetCallback(callback));
}
public Hresult RzSBGestureSetCallback(TouchpadGestureCallbackFunction callback) {
return Hresult.getFromApiValue(lib.RzSBGestureSetCallback(callback));
}
// These are the methods in the interface RazerLibrary.java file passed to JNA
int RzSBAppEventSetCallback(RazerAPI.AppEventCallbackFunction callback);
int RzSBDynamicKeySetCallback(RazerAPI.DynamicKeyCallbackFunction callback);
int RzSBKeyboardCaptureSetCallback(RazerAPI.KeyboardCallbackFunction callback);
int RzSBGestureSetCallback(RazerAPI.TouchpadGestureCallbackFunction callback);
注册回调时,会使用与此类似的内容(简化为不包含数百行代码,文章末尾有指向完整代码文件的链接)。
public class RazerManager implements DynamicKeyCallbackFunction {
private static DynamicKeyCallbackFunction dkCallback;
private RazerManager() {
dkCallback = this;
RazerAPI api = RazerAPI.INSTANCE;
api.RzSBDynamicKeySetCallback(dkCallback);
}
@Override
public int callback(int type, int state) {
System.out.printf("DK Callback: %s %s", type, state);
return 0; // S_OK
}
}
public class Touchpad implements TouchpadGestureCallbackFunction {
private static TouchpadGestureCallbackFunction gestureCallback;
private Touchpad() {
gestureCallback = this;
RazerAPI api = RazerAPI.INSTANCE;
api.RzSBGestureSetCallback(gestureCallback);
}
@Override
public int callback(int gestureType, UINT param, USHORT x, USHORT y, USHORT z) {
System.out.printf("Gesture: %s", gestureType);
}
}
RazerManager 实例在用于测试的简单 Swing 应用程序的主循环中创建,RazerManager 在其构造函数中创建 Touchpad 实例。使用 while 循环处理消息,GetMessage 后跟 Translate 和 DispatchMessage(如果有效),如下所示:
public class Main {
public static void main(String[] args) throws IOException {
// call javax's invokeLater to run app
}
public Main() {
/* JFrame created here and then shown with setVisible(true); */
RazerManager manager = RazerManager.getInstance();
// Yes this is horrible, blocking everything but it's simply
// used to get messages to go through and trigger the callbacks
WinUser.MSG msg = new WinUser.MSG();
while (true) {
int hasMessage = User32.INSTANCE.GetMessage(msg, null, 0, 0);
if (hasMessage != 0) {
User32.INSTANCE.TranslateMessage(msg);
User32.INSTANCE.DispatchMessage(msg);
}
}
}
}
现在,手势事件处理得很好,Touchpad 类中的回调被调用并正确返回。当生成动态按键事件时,在处理windows消息的while循环内会抛出异常:
Exception in thread "AWT-EventQueue-0" java.lang.Error: Invalid memory access
at com.sun.jna.Native.invokeInt(Native Method)
at com.sun.jna.Function.invoke(Function.java:383)
at com.sun.jna.Function.invoke(Function.java:315)
at com.sun.jna.Library$Handler.invoke(Library.java:212)
at com.sun.proxy.$Proxy11.DispatchMessage(Unknown Source)
at com.sharparam.jblade.tester.Main.<init>(Main.java:113)
at com.sharparam.jblade.tester.Main$1.run(Main.java:25)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:251)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:733)
at java.awt.EventQueue.access$200(EventQueue.java:103)
at java.awt.EventQueue$3.run(EventQueue.java:694)
at java.awt.EventQueue$3.run(EventQueue.java:692)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:703)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:242)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:161)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:150)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:146)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:138)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:91)
这只发生在动态键事件上,而不是手势或应用事件。我一生都无法理解为什么,因为动态键和手势回调在 C++ 端非常相似。
编辑:完整的代码可以在GitHub repo 中找到,特别是RazerLibrary.java、RazerAPI.java、RazerManager.java 和Touchpad.java。挥杆测试可以通过gist 找到。
【问题讨论】:
-
仔细查看关键事件结构/联合映射。您还可以通过使用带有
Pointer参数的 Translate/DispatchMessage 版本来避免不必要的内存读取/写入(因为您实际上并不关心消息内容)。 -
你到底是什么意思? JNA 的 Translate/DispatchMessage 方法只带一个 MSG 参数,找不到带指针的重载。
-
您可以通过扩展现有接口轻松地制作自己的“重载”。您不喜欢带有
Pointerarg 的方法吗?扩展接口以使用MyStructure参数声明相同的方法。 -
因此,如果我理解正确,我会创建看起来相同的重载,而不是用指针 arg 代替 MSG。但是使用它们,它看起来会像当前使用指针代替 MSG 的 while 循环设置吗?当前的代码发生了一些变化,如下所示:gist.github.com/Sharparam/ce0c8d2f04308d4c27f7(消息循环见第 111 行)
-
正确。您将使用
Pointer代替WinUser.MSG。使用原始指针还意味着 JNA 不会费心尝试将内存从本机复制回本机(因为您从不使用 Java 端的细节)。
标签: java c++ winapi callback jna