Search code examples
javac++java-native-interfacehookjna

java call Hook by jna/ jni failed


I need to disable the mouse and keyboard input globally, but using pure java couldn't do the trick.

I chose jni/jna call c++ function. When I call a c++ function through jna, java program has no effect, without any error or exception.

I have tested the .dll file, in c++ it can't run perfectly.

Here is my java code:

public class HookTest {
    public interface  Hook extends Library{
        Hook INSTANCE = (Hook) Native.loadLibrary("lib/Hook",Hook.class);
        public void FuncEndHook();
        public void FuncHookDevice();
    }

    public static void main(String[] args) {
        Hook.INSTANCE.FuncHookDevice();
    }
}

my c++ code:

// dllmain.cpp : Defines the entry point for the DLL application.
#include "stdafx.h"
#include "stdio.h"

HHOOK g_HookHwnd = NULL;
HHOOK g_hMouse = NULL;

// 钩子子程
extern "C" _declspec(dllexport) LRESULT CALLBACK MyHookFun(int nCode, WPARAM wParam, LPARAM lParam)
{
    printf("in hook key function\n");
    // 这个Structure包含了键盘的信息
    /*typedef struct {
    DWORD vkCode;
    DWORD scanCode;
    DWORD flags;
    DWORD time;
    ULONG_PTR dwExtraInfo;
    } KBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT;*/
    // 我们只需要那个vkCode
    PKBDLLHOOKSTRUCT pVirKey = (PKBDLLHOOKSTRUCT)lParam;

    // MSDN说了,nCode < 0的时候别处理
    if (nCode >= 0)
    {
        // 按键消息
        switch (wParam)
        {
        case WM_KEYDOWN:
        case WM_SYSKEYDOWN:
        case WM_KEYUP:
        case WM_SYSKEYUP:
            switch (pVirKey->vkCode)
            {
            case VK_LWIN:
            case VK_RWIN:
                return 1;  // 吃掉消息
                break;
            }
            return 1;
            break;
        }
    }

    return CallNextHookEx(g_HookHwnd, nCode, wParam, lParam);
}

extern "C" _declspec(dllexport) LRESULT CALLBACK  MyHookMouse(int nCode, WPARAM wParam, LPARAM lParam)
{
    printf("in hook mouse function\n");
    return 1;
}


HMODULE g_Module;
extern "C" _declspec(dllexport)  BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
    {

        break;
    }
    }
    return TRUE;
}


extern "C" _declspec(dllexport) void FuncHookDevice()
{
    if (!g_HookHwnd)
    {
        printf("start hook\n");
        g_HookHwnd = SetWindowsHookEx(WH_KEYBOARD_LL, MyHookFun, g_Module, 0);
        //g_hMouse = SetWindowsHookEx(WH_MOUSE_LL, MyHookMouse, g_Module, 0);  //暂时禁用鼠标钩子
    }
}

extern "C" _declspec(dllexport) void FuncEndHook()
{
    printf("end hook\n");
    UnhookWindowsHookEx(g_HookHwnd);
    //UnhookWindowsHookEx(g_hMouse);   //暂时禁用鼠标钩子
    g_HookHwnd = NULL;
}

running result:

result

The program executed the c++, but did not disable the keyboard.

Thanks for reading and for your help.


Solution

  • Check out what the remarks section of the LowLevelKeyboardProc callback function says:

    This hook is called in the context of the thread that installed it. The call is made by sending a message to the thread that installed the hook. Therefore, the thread that installed the hook must have a message loop.

    You've created a hook for an event, but there's no message loop processing those events. All Windows applications that use events, such as graphical desktop applications which use native Windows styles, have a message loop which calls GetMessage to get events and act accordingly.

    You could manually call the WinAPI functions to handle the message loop (GetMessage, DispatchMessage, etc.), but I'm happy to say that JNA does have a helper for this.

    In the jna-platform package (you'll need to add that to your Maven/Gradle/whatever dependencies), there's a class named User32Util.MessageLoopThread.

    The following example works for me:

    import com.sun.jna.Library;
    import com.sun.jna.Native;
    import com.sun.jna.platform.win32.User32Util.MessageLoopThread;
    
    public class HookTest {
    
        public interface Hook extends Library {
            Hook INSTANCE = (Hook) Native.loadLibrary("lib/Hook", Hook.class);
            public void FuncEndHook();
            public void FuncHookDevice();
        }
    
        public static void main(String[] args) {
            MessageLoopThread thread = new MessageLoopThread();
            thread.start();
            thread.runOnThread(() -> {
                Hook.INSTANCE.FuncHookDevice();
                return null;
            });
        }
    }
    

    Notice I'm explicitly calling FuncHookDevice on a new thread which is the same thread as the message loop, as required by the remarks.