【问题标题】:C++ binary to Java gets "java.lang.Error: Invalid memory access"Java 的 C++ 二进制文件获取“java.lang.Error: Invalid memory access”
【发布时间】: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.javaRazerAPI.javaRazerManager.javaTouchpad.java。挥杆测试可以通过gist 找到。

【问题讨论】:

  • 仔细查看关键事件结构/联合映射。您还可以通过使用带有 Pointer 参数的 Translate/DispatchMessage 版本来避免不必要的内存读取/写入(因为您实际上并不关心消息内容)。
  • 你到底是什么意思? JNA 的 Translate/DispatchMessage 方法只带一个 MSG 参数,找不到带指针的重载。
  • 您可以通过扩展现有接口轻松地制作自己的“重载”。您不喜欢带有Pointer arg 的方法吗?扩展接口以使用 MyStructure 参数声明相同的方法。
  • 因此,如果我理解正确,我会创建看起来相同的重载,而不是用指针 arg 代替 MSG。但是使用它们,它看起来会像当前使用指针代替 MSG 的 while 循环设置吗?当前的代码发生了一些变化,如下所示:gist.github.com/Sharparam/ce0c8d2f04308d4c27f7(消息循环见第 111 行)
  • 正确。您将使用Pointer 代替WinUser.MSG。使用原始指针还意味着 JNA 不会费心尝试将内存从本机复制回本机(因为您从不使用 Java 端的细节)。

标签: java c++ winapi callback jna


【解决方案1】:

所以事实证明你不能(或者至少你必须以与我不同的方式来做)让一个类实现多个回调接口。创建不同回调接口的显式实现并将它们分配给 RazerManager 中的回调字段解决了这个问题。

这解释了为什么 Touchpad 中的回调有效,但 RazerManager 中的回调无效(Touchpad 实现了一个接口,而 RazerManager 实现了三个)。

演示:

public class MyClass {
  private static MyCallbackInterface myCallback;

  private MyClass() {
    myCallback = new CallbackInterface() {
      @Override
      public int callback(/* parameters */) {
        // Do stuff with data here
        return 0;
      }
    }

    nativeLib.SetCallback(myCallback);
  }

当单个类实现多个回调接口并且不知道调用哪个回调接口时,本地库或 JNA 似乎在某个地方感到困惑。所以它随机调用一个(或第一个定义的?)。

【讨论】:

  • 有趣的问题。您是正确的,因为 JNA 将在它找到的派生自 Callback 的第一个接口上使用名为 callback 的第一个方法。在大多数情况下,预计回调将有一个公共方法用作回调方法,在这种情况下,实际的方法名称可以采用任何值。
猜你喜欢
  • 2015-06-07
  • 2012-07-08
  • 1970-01-01
  • 1970-01-01
  • 2017-10-01
  • 2011-09-03
  • 2016-01-15
  • 1970-01-01
相关资源
最近更新 更多