0

我正在编写一个位于系统托盘中的程序。在任何打开的应用程序中,它将有一个键盘挂钩到所有键入的输入。我希望它拦截任何输入的输入,在该输入上运行代码,然后发送要输入的新的“正确”字符。这个新角色将出现在具有焦点的应用程序中。

我通过 StackOverflow 上的另一个问题找到了这段代码,它看起来不错。 http://blogs.msdn.com/toub/archive/2006/05/03/589423.aspx

我将它作为一个类添加到我的解决方案中,在 Form1 构造函数中创建了它的一个新实例,并且它可以编译。任何应用程序的任何键入输入都会显示在 Visual Studio 输出窗格中,一次一个字符。

问题是这段代码超出了我的想象。这就是为我的键盘挂钩使用这个简洁的代码的缺点:我没有反复试验来教我如何使用它。

我设想这个程序是这样工作的:

key is pressed, triggers an event

get key information from the event

do computation on the key information, pick the character to be typed

send that key to the relevant program

my character is typed rather than the original keypress character

这段代码如何适应这一系列事件?我需要在输入之前读取输入字符。那么,分析输入并确定正确输入的代码将在哪里进行呢?最后,调用什么正确的“sendInput()”类型命令来将字符发送到任何有焦点的应用程序?

这是完整的代码供参考:

using System;
using System.Diagnostics;
using System.Windows.Forms;
using System.Runtime.InteropServices;

class InterceptKeys
{
    private const int WH_KEYBOARD_LL = 13;
    private const int WM_KEYDOWN = 0x0100;
    private static LowLevelKeyboardProc _proc = HookCallback;
    private static IntPtr _hookID = IntPtr.Zero;

public static void Main()
{
    _hookID = SetHook(_proc);
    Application.Run();
    UnhookWindowsHookEx(_hookID);
}

private static IntPtr SetHook(LowLevelKeyboardProc proc)
{
    using (Process curProcess = Process.GetCurrentProcess())
    using (ProcessModule curModule = curProcess.MainModule)
    {
        return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
            GetModuleHandle(curModule.ModuleName), 0);
    }
}

private delegate IntPtr LowLevelKeyboardProc(
    int nCode, IntPtr wParam, IntPtr lParam);

private static IntPtr HookCallback(
    int nCode, IntPtr wParam, IntPtr lParam)
{
    if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
    {
        int vkCode = Marshal.ReadInt32(lParam);
        Console.WriteLine((Keys)vkCode);
    }
    return CallNextHookEx(_hookID, nCode, wParam, lParam);
}

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook,
    LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
    IntPtr wParam, IntPtr lParam);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
}

感谢您的任何建议!或者,有没有更好的方法来解决这个问题?

谢谢!


更新


我的 HookCallback 方法现在看起来像这样:

    private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
        {
            //Console.WriteLine((Keys)vkCode);

            KBDLLHOOKSTRUCT replacementKey = new KBDLLHOOKSTRUCT();
            Marshal.PtrToStructure(lParam, replacementKey);
            replacementKey.vkCode = 90; // char 'Z'
            Marshal.StructureToPtr(replacementKey, lParam, true);

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

射击...添加Marshal.StructureToPtr已接近,但会导致A first chance exception of type 'System.ArgumentException' occurred in foobar.exe错误。


带有不安全代码、无编组等的版本(仍然不输出“Z”!)


    unsafe private static IntPtr HookCallback(int nCode, IntPtr wParam, void* lParam)
    {
        if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
        {
            KBDLLHOOKSTRUCT* replacementKey = (KBDLLHOOKSTRUCT*)lParam;
            replacementKey->vkCode = 90;
        }
        return CallNextHookEx(_hookID, nCode, wParam, (IntPtr) lParam);
    }
4

2 回答 2

1

这段代码如何适应这一系列事件?我需要在输入之前读取输入字符。那么,分析输入并确定正确输入的代码将在哪里进行呢?

HookCallback方法是您要安装以拦截击键的方法。

最后,调用什么正确的“sendInput()”类型命令来将字符发送到任何有焦点的应用程序?

要修改传递给其他进程的字符,请在将参数(nCodewParam和/或lParam)传递给CallNextHookEx方法之前对其进行修改。

于 2010-01-12T08:29:24.990 回答
1

目前,这段代码只是写入控制台。这发生在 HookCallback 方法的第 4 行。所以你需要用你自己的代码替换那行。

但是,您获得的信息不是 C# 友好的格式。您需要查看LowLevelKeyboardProc的 SDK 文档以了解如何解压它。事实证明,所有好东西都在 lParam 中,它是一个指向 KBDLLHOOKSTRUCT 结构的指针。

因此,您的代码需要将其编组为 C# 等效项。您需要声明一个 KBDLLHOOKSTRUCT 结构,或者查看 pinvoke.net 上是否有一个,然后使用 Marshal.PtrToStructure 对其进行解包。然后,您可以在 Windows 允许的范围内尽可能多地使用这种结构——我认为修改事件数据比试图杀死键盘事件然后模拟一个新事件更好(首先,我认为模拟的“发送键” " 会直接进入钩子本身!) - 但是我还没有尝试过这个,看看它有什么可能。SDK 结构确实包含虚拟键码和扫描码,因此您可以替换它们以达到您想要的效果,但这可能需要一些试验和错误,并且将非常特定于您的应用程序。

于 2010-01-12T08:41:10.117 回答