jjzjj

c# - VSTO Windows Hook keydown 事件调用 10 次

coder 2024-06-04 原文

因此,我一直在开发一个类来处理 VSTO 加载项中的 Kwyboard 输入,到目前为止,我一直在使用 Windows Hook 来完成此操作并取得了相对成功。

有这段代码:

    //.....
    private const int WH_KEYBOARD = 2;
    private const int WH_MOUSE = 7;

    private enum WM : uint {
        KEYDOWN = 0x0100,
        KEYFIRST = 0x0100,
        KEYLAST = 0x0108,
        KEYUP = 0x0101,
        MOUSELEFTDBLCLICK = 0x0203,
        MOUSELEFTBTNDOWN = 0x0201,
        MOUSELEFTBTNUP = 0x0202,
        MOUSEMIDDBLCLICK = 0x0209,
        MOUSEMIDBTNDOWN = 0x0207,
        MOUSEMIDBTNUP = 0x0208,
        MOUSERIGHTDBLCLK = 0x0206,
        MOUSERIGHTBTNDOWN = 0x0204,
        MOUSERIGHTBTNUP = 0x0205
    }

    private hookProcedure proc;

    private static IntPtr hookID = IntPtr.Zero;

    //Enganches

    [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    private static extern IntPtr SetWindowsHookEx(int hookId, hookProcedure proc, IntPtr hInstance, uint thread);

    [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    private static extern bool unHookWindowsHookEx(int hookId);

    [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    private static extern IntPtr CallNextHookEx(IntPtr hookId, int ncode, IntPtr wparam, IntPtr lparam);

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

    [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern int GetCurrentThreadId();

    public CPInputListener() {

        proc = keyBoardCallback;

        hookID = setHook(proc);
    }


    private IntPtr setHook(hookProcedure procedure){

        ProcessModule module = Process.GetCurrentProcess().MainModule;
        uint threadId = (uint)GetCurrentThreadId();

        return SetWindowsHookEx(WH_KEYBOARD, procedure, IntPtr.Zero, threadId);
    }

    public void stopListeningAll() {
        unHookWindowsHookEx(WH_KEYBOARD);//For now
    }


    private IntPtr keyBoardCallback(int ncode, IntPtr wParam, IntPtr lParam) {

        if (ncode >= 0) {
            //LPARAM pretty useless

            Keys key = (Keys)wParam;

            KeyEventArgs args = new KeyEventArgs(key);

            onKeyDown(args);//for now

        }
        return CallNextHookEx(hookID, ncode, wParam, lParam);
    }
    //....

我确实成功接收了键盘输入,但这是个大谜团;每次按下一个键,无论它有多快,事件 (onKeyDown) 都会准确调用 10 次,不多也不少。

如果长按该键,该事件将继续被调用,但会调用 10 次 10 次,而不是只调用一次。

到目前为止我已经尝试过

  1. 使用 wParam 在 Key Up 上调用所需的事件:似乎不起作用,在我看到的所有处理 Key down 和 up 事件的代码中,都使用了 IntPtr wParam,但是来自该变量我只能检索没有帮助的键码。
  2. 使用 lParamnCode:这些变量在这 10 次调用之间给出了不一致的值,ncode 倾向于检索 0 和 3,并且 lParam 一些值似乎是非托管内存地址...

我期待什么

我确实希望 onKeyDown 在按下键时只被调用一次,或者另一方面能够通过 on key up 调用该方法,我希望每次释放键时只调用一次。

如何绕过这个

如果我找不到合理的答案,我正在考虑使用自定义计时器来丢弃所有这些调用并仅使用最后一个,如果其他一切都失败了,你会推荐这个吗?

非常感谢!要快乐,要善良! :D

最佳答案

首先,您必须过滤正确的 ncode 以仅获取您应该处理的击键。 (例如,您不应该处理 HC_NOREMOVE。)
然后您必须使用 lParam 中的标志检查它是 KeyDown 还是 KeyUp 事件。

如果按键被长按,多个 KeyDown 事件已经被 Win32 合并为一个调用,因此您不必在这里做任何特殊的事情。但是,如果您只想获得最后一个 KeyUp 事件,那么您还必须检查 lParam 中的另一个标志。

因此,这是您需要更改的代码:

private IntPtr keyBoardCallback(int ncode, IntPtr wParam, IntPtr lParam)
{
    // Feel free to move the const to a private field.
    const int HC_ACTION = 0;
    if (ncode == HC_ACTION)
    {
        Keys key = (Keys)wParam;
        KeyEventArgs args = new KeyEventArgs(key);

        bool isKeyDown = ((ulong)lParam & 0x40000000) == 0;
        if (isKeyDown)
            onKeyDown(args);
        else
        {
            bool isLastKeyUp = ((ulong)lParam & 0x80000000) == 0x80000000;
            if (isLastKeyUp)
                onKeyUp(args);
        }
    }
    return CallNextHookEx(hookID, ncode, wParam, lParam);
}

根据评论中的要求进行编辑:
不幸的是,这些参数的文档非常少。

可以找到一个“提示”不要处理除 HC_ACTION 之外的任何内容 here ,说明:

if (nCode < 0)  // do not process message
    return ...;

// ...
switch (nCode) 
{
    case HC_ACTION:
        // ... do something ...
        break;

    default:
        break;
}
// ...
return CallNextHookEx(...);

这里还有一个支持声明:
Why does my keyboard hook receive the same key-up and key-down events multiple times?

lParam 的内容定义为as follows :

typedef struct tagKBDLLHOOKSTRUCT {
    DWORD     vkCode;
    DWORD     scanCode;
    DWORD     flags;
    DWORD     time;
    ULONG_PTR dwExtraInfo;
}

(提醒一下:DWORD 在 x86 和 x64 平台上的大小都是 4 bytes。)

可以找到lParam flags 的文档herehere .
在这个链接中描述了

  • 第 30 位 (=0x40000000) 是上一个键状态
    (1 如果键 按下,0 如果键 之前导致此调用的新关键状态)
  • bit 31 (=0x80000000) 是过渡状态
    (0 按键按下,1 按键释放现在)

术语“上一个关键状态”相当令人困惑,但实际上它恰好与当前状态相反(因为只有向上或向下,没有第三种状态)。

当“键盘的自动重复功能”被激活时,即当按键被按下足够长的时间时,转换状态尤其重要。

可以找到另一个示例(使用 VC7)here :

if (HIWORD (lParam) & 0xC000)
    // Key up without autorepeat
else
    // Key down

0xC000 就是 0x4000 || 0x8000 并定义键已释放并已创建键弹起事件。

总而言之,这很令人困惑,但仍然是事实。
也许还有其他链接可以更好地描述这种情况,但我想在这样的时代,新的应用程序开发“应该在小型沙箱(如 UWP)中完成”,而 VSTO 肯定会死去为newer Office add-ins写在HTML and JavaScript ,没有人再那么关心低级钩子(Hook)了。

关于c# - VSTO Windows Hook keydown 事件调用 10 次,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40599162/

有关c# - VSTO Windows Hook keydown 事件调用 10 次的更多相关文章

  1. 使用 ACL 调用 upload_file 时出现 Ruby S3 "Access Denied"错误 - 2

    我正在尝试编写一个将文件上传到AWS并公开该文件的Ruby脚本。我做了以下事情:s3=Aws::S3::Resource.new(credentials:Aws::Credentials.new(KEY,SECRET),region:'us-west-2')obj=s3.bucket('stg-db').object('key')obj.upload_file(filename)这似乎工作正常,除了该文件不是公开可用的,而且我无法获得它的公共(public)URL。但是当我登录到S3时,我可以正常查看我的文件。为了使其公开可用,我将最后一行更改为obj.upload_file(file

  2. c# - 如何在 ruby​​ 中调用 C# dll? - 2

    如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

  3. C# 到 Ruby sha1 base64 编码 - 2

    我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

  4. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  5. ruby - 调用其他方法的 TDD 方法的正确方法 - 2

    我需要一些关于TDD概念的帮助。假设我有以下代码defexecute(command)casecommandwhen"c"create_new_characterwhen"i"display_inventoryendenddefcreate_new_character#dostufftocreatenewcharacterenddefdisplay_inventory#dostufftodisplayinventoryend现在我不确定要为什么编写单元测试。如果我为execute方法编写单元测试,那不是几乎涵盖了我对create_new_character和display_invent

  6. ruby-on-rails - 事件管理员日期过滤器日期格式自定义 - 2

    是否有简单的方法来更改默认ISO格式(yyyy-mm-dd)的ActiveAdmin日期过滤器显示格式? 最佳答案 您可以像这样为日期选择器提供额外的选项,而不是覆盖js:=f.input:my_date,as::datepicker,datepicker_options:{dateFormat:"mm/dd/yy"} 关于ruby-on-rails-事件管理员日期过滤器日期格式自定义,我们在StackOverflow上找到一个类似的问题: https://s

  7. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  8. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  9. C51单片机——实现用独立按键控制LED亮灭(调用函数篇) - 2

    说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时

  10. ruby-on-rails - 事件记录 : Select max of limit - 2

    我正在尝试将以下SQL查询转换为ActiveRecord,它正在融化我的大脑。deletefromtablewhereid有什么想法吗?我想做的是限制表中的行数。所以,我想删除少于最近10个条目的所有内容。编辑:通过结合以下几个答案找到了解决方案。Temperature.where('id这给我留下了最新的10个条目。 最佳答案 从您的SQL来看,您似乎想要从表中删除前10条记录。我相信到目前为止的大多数答案都会如此。这里有两个额外的选择:基于MurifoX的版本:Table.where(:id=>Table.order(:id).

随机推荐