什么是“钩子”,我如何在Java中编写它?如何与内核通信以了解用户按下的键/在操作系统中注册?

13
虽然我搜索了很多,但我仍然不清楚“钩子”到底是什么。例如,我在维基答案上读到了这篇文章:
引用: 钩子是一种将代码片段插入到另一个代码片段前面的方法,以便第一个代码片段在第二个代码片段执行之前执行,使第一个代码片段有机会监视和/或过滤第二个代码片段的行为。 一个例子可能是鼠标钩子,允许钩子代码在保留原始鼠标事件处理程序功能的同时监视鼠标。
我还读了这篇文章,但我仍然不明白“钩子”到底是什么。请有人用通俗易懂的语言解释一下,“钩子”是什么?为什么有些人要写“钩子”?此外,是否可以在Java中编写“钩子”?
注意:
我想在Java中编写按键记录器,我的一个朋友说你必须在C中编写“钩子”。我不能完全在Java中编写键盘记录器(仅在Windows上运行)吗?
编辑
请针对按键记录器回答。如何使用钩子要求内核向我的应用程序提供有关按下的键的信息?或者我如何使用JNI向操作系统注册我的应用程序? 我希望我的应用程序能够记录用户按下的键。

1
我认为你的朋友的意思是你需要使用本地方法。你可以用C语言编写一个本地方法,并使用Java本地接口(JNI)从Java中调用它。 - toto2
@toto2,那个本地方法会做什么? - saplingPro
那将是一个记录键的系统功能。实际上,对于这样的工作,你最好完全放弃Java。Java旨在具有可移植性,缺点是它无法执行与操作系统相关的任务。我不太了解Windows,但可能C#更合适。 - toto2
@grassPro 你最好重新编辑这个问题,并标记上与你的操作系统相关的标签,因为这是你正在寻找的主要知识。 - MaDa
8个回答

7
我将词语“hook”与至少两个不同的概念联系起来:
a) 观察者模式,其中一个类允许您添加侦听器,在特定事件发生时将通知您。您可以在Swing、Servlet API和许多第三方框架中找到它。
b) 模板方法模式。抽象类定义以何种顺序调用哪些方法,实现类可以覆盖这些方法。这方面的示例并不是非常常见,但你偶尔会见到它们。

6

来自Hooking - 维基百科

在计算机编程中,Hooking 这个术语涵盖了一系列的技术,用于通过截取操作系统、应用程序或其他软件组件之间传递的函数调用、消息或事件来改变或增强它们的行为。处理这些截取的函数调用、事件或消息的代码称为“钩子”。

Java 中一个很好的例子是 Runtime.addShutdownHook。一个 shutdown hook 只是一个已初始化但未启动的线程。当虚拟机开始其关闭序列时,它将以某个未指定的顺序启动所有已注册的关闭挂钩,并让它们并发地运行。

Runtime.addShutdownHook(new Thread(){
    @Override
    public void run(){
        // do something before application terminates
    }
});

1

这里是一个简单的例子:

public void hookableMethod(HookObject hook, String param) { 
String innerParam = param;
if(hook != null) { 
 innerParam = hook.someHookMethod(innerParam);  
}

//rest of the code is working with inner param
}

在这个过于简化的例子中,hookableMethod提供了一种方法让外部实体钩住它-也就是传递外部对象引用,这个外部对象将首先被执行。因此,如果您需要进行某种不属于原始实现的处理,可以通过传递引用而不是修补原始源代码来实现。

1
在您的情况下,钩子是一种软件机制,用于在操作系统中注册侦听器(处理程序)。当键事件被触发(按键)时,操作系统将通过该处理程序(例如应用程序中的方法)通知它。这只是观察者模式的一个应用。
一个虚构的例子:
    class MyKBHandler implements ISomeKeyboardListener {
        public void onKeyDown(<params containing key info here>){
            //Your code here, doing something with the params.
        }
    }

    MyKBHandler handler = new MyKBHandler();
    <someOSLibrary>.register(handler);

正如其他答案中所告诉你的那样,如果不依赖于一些本地代码,Java是没有办法实现这个的。我认为最简单的方法是选择与您正在针对的操作系统相关的语言。对于 Windows,我会使用 C# 来实现。
请查看此链接:

http://msdn.microsoft.com/en-us/library/ms632589%28VS.85%29.aspx


我该如何与内核进行通信? - saplingPro
需要与内核通信吗?不必如此。只需向操作系统注册,它将在每次按键时调用您的处理程序。在这个页面http://en.wikipedia.org/wiki/Hooking上,您可以找到C#键记录器的示例,还有很多开源代码在网络上随处可见。 - Mister Smith
@ Smith 先生,我想使用 JNI 注册我的应用程序。也就是说,我想使用 Java 和本地代码特别是 C 来实现这一点。这就是问题。现在,我该如何向操作系统注册,以便在每次键入时它都会调用我的处理程序。 - saplingPro

1

这个的其他名称可能是委托或回调。

Java中的一个例子是MouseListener( http://download.oracle.com/javase/tutorial/uiswing/events/mouselistener.html)。 MouseListener是具有诸如mouseClicked(MouseEvent e)之类的方法的接口。为了响应用户的鼠标动作,您将实现MouseListener并在用户单击时,Java Swing库将调用您的监听器类(在通过调用addMouseListener注册它之后)。


1
关于键盘记录器:是的,你可以用Java编写大部分代码,但是一小部分需要使用JNI模块,因为例如全局钩子会将DLL /线程/其他内容“注入”到其他进程中...由于并非所有进程都托管JVM等,这在Java中无法实现...因此,您需要使用JNI和一些C来编写该部分键盘记录器的DLL...
编辑-根据评论:
本文深入描述了全局钩子的作用以及注入等含义...

@Yahia,"全局钩子是指将一个DLL/线程/其他被'注入'到其他进程中的组件.." 你能否更清楚地解释一下,并在回答中包含该部分? - saplingPro

1

捕获低级设备事件需要运行本地代码(JNI,JNA等),并且高度依赖于系统。在Linux上,您可能不需要与内核通信,而是与X11服务器通信。


0

首先您需要定义一个管理器类,大致如下:

package foo.bar

public class SomeHookManager {
    public static void initialize (...) {
        // <pre-initialization-routing>
        _native_init_();
        // <post-initialization-routing>
    }

    public static void registerCallback (IHookCallback callback)
    { /* save this callback */ }

    // this method will be invoked in your C code.
    protected static void invokeCallback () { /* invoke the saved callback */ }

    // this is a native method, the native modifier tells the compiler this method
    // is implemented in C, and linked at runtime.
    protected native static void _native_init_ ();
}

然后使用 javah 生成一个 C 头文件,结果类似于(我并没有实际编译这段代码,因此这只是假设):

/*
 * Class:     Win32
 * Method:    _native_init_
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_foo_bar_Win32_native_init_
    (JNIEnv * env, jobject obj);

创建一个C项目,包含此文件并实现此方法。确保在实际钩子触发时,在您的C代码中调用invokeCallback
jclass cls = (*env)->GetObjectClass(env, obj);
jmethodID mId = (*env)->GetStaticMethodID(env, cls, "invokeCallback", "()V");
if (mId == 0) { /* error handling */ }
(*env)->CallStaticVoidMethod(env, cls, mId);

把C项目编译成一个DLL文件,比如说hookimpl_win32.dll,然后在Java代码的某个位置进行动态链接:

static {
    System.loadLibrary("hookimpl_win32"); // no need of .dll or .so in Unix alike OS's
}

确保dll文件与您的jar文件在同一文件夹中。或者在VM参数中指定-Djava.library.path=/path/to/your/dlls

至于如何记录每个按键,操作系统通常提供一些API来拦截键盘事件。对于Windows系统,您可以通过拦截全局键消息来实现此目的。我没有其他系统的经验。无论如何,您可以设置适当的中断,这是相当低级的。您总是可以通过谷歌找到答案。 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接