2014-01-27 72 views
2

我有一個Java庫接口(通過JNA)與本機C + + DLL。這個DLL提供函數來設置在發生某些硬件事件時調用的回調函數。C++二進制到Java獲取「java.lang.Error:無效的內存訪問」

所有這些回調工作,除了一個,即使它與另一個具有幾乎相同的定義。

這些回調簽名和枚舉在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 /庫類/接口這些功能設置它們:

// 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的一個實例中使用的用於測試的簡單擺動的應用程序的主循環中創建,RazerManager在其構造創建觸摸板的一個實例。 while循環用於處理具有的GetMessage消息隨後翻譯和在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); 
     } 
    } 
    } 
} 

現在,手勢事件的處理就好了,在觸控板類回調函數被調用和返回正常。當產生一個動態鍵事件,則拋出異常的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) 

這僅具有動態按鍵事件發生時,不與手勢或appevent。我無法理解爲什麼,因爲動態鍵和手勢回調在C++端非常相似。

編輯:完整代碼可以在GitHub repo可以發現,特別RazerLibrary.javaRazerAPI.javaRazerManager.javaTouchpad.java。揮杆測試可以發現爲gist

+0

仔細觀察關鍵事件結構/聯合映射。您還可以通過使用帶有「指針」參數的Translate/DispatchMessage版本來避免不必要的內存讀取/寫入(因爲您實際上並不關心消息內容)。 – technomage

+0

你是什麼意思? JNA的Translate/DispatchMessage方法僅使用MSG參數,找不到需要指針的重載。 – Sharparam

+0

只需簡單地通過擴展現有接口,就可以輕鬆製作自己的「過載」。你不喜歡'Pointer' arg的那種方法嗎?擴展接口以用'MyStructure'參數聲明相同的方法。 – technomage

回答

1

所以事實證明,你不能(或者至少你必須以不同於我做的方式來做)有一個類實現多個回調接口。創建不同回調接口的顯式實現並將它們分配給RazerManager中的回調字段解決了這個問題。

這解釋了爲什麼觸摸板的回調工作正常,但不是RazerManager中的回調(觸摸板實現了一個界面,而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被混淆的地方,不知道調用哪一個。所以它隨機調用一個(或第一個定義?)。

+0

有趣的問題。你是正確的,因爲JNA會在第一個從'Callback'派生的接口上使用名爲'callback'的第一個方法。在大多數情況下,預計回調將使用單個公共方法作爲回調方法,在這種情況下,實際方法名稱可能具有任何價值。 – technomage