You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Delphi下键盘钩子捕获死键:寻求MSB操作修复方案的等效实现

解决Delphi低级别键盘钩子捕获死键的问题

你提到的死键(比如输入带重音字符时的前置键,例如´)无法被当前代码捕获,核心原因是死键的处理需要结合键盘状态,并且在调用ToAsciiEx时要正确处理扫描码的最高有效位(MSB)。下面是修改后的完整Delphi实现,完美支持死键捕获:

program Project1;
{$APPTYPE CONSOLE}
{$R *.res}

uses
  Windows, Messages, SysUtils;

var
  hhk: HHOOK;
  Msg: TMsg;

{==================================================================================}
function GetCharFromVirtualKey(Key: Word; ScanCode: Word; var KeyboardState: TKeyboardState): string;
var
  keyboardLayout: HKL;
  asciiResult: Integer;
begin
  keyboardLayout := GetKeyboardLayout(0);
  SetLength(Result, 2);
  // 关键操作:为扫描码设置最高有效位(MSB),让ToAsciiEx正确识别死键
  if (ScanCode and $80) = 0 then
    ScanCode := ScanCode or $80;
  asciiResult := ToAsciiEx(Key, ScanCode, KeyboardState, @Result[1], 0, keyboardLayout);
  case asciiResult of
    0: Result := '';
    1: SetLength(Result, 1);
    2: 
      begin
        // 死键触发时返回标记字符,后续按键会结合此状态生成最终组合字符
        SetLength(Result, 1);
      end;
  else
    Result := '';
  end;
end;
{==================================================================================}

function LowLevelKeyboardProc(nCode: Integer; wParam: wParam; lParam: lParam): LRESULT; stdcall;
type
  PKBDLLHOOKSTRUCT = ^TKBDLLHOOKSTRUCT;
  TKBDLLHOOKSTRUCT = record
    vkCode: cardinal;
    scanCode: cardinal;
    flags: cardinal;
    time: cardinal;
    dwExtraInfo: Cardinal;
  end;
var
  LKBDLLHOOKSTRUCT: PKBDLLHOOKSTRUCT;
  keyboardState: TKeyboardState;
  charResult: string;
begin
  // 先调用下一个钩子,保证系统正常处理
  Result := CallNextHookEx(hhk, nCode, wParam, lParam);
  case nCode of
    HC_ACTION:
    begin
      // 同时监听WM_KEYDOWN和WM_DEADCHAR,覆盖死键的两种触发场景
      if (wParam = WM_KEYDOWN) or (wParam = WM_DEADCHAR) then
      begin
        LKBDLLHOOKSTRUCT := PKBDLLHOOKSTRUCT(lParam);
        // 获取当前键盘状态,确保死键状态能被后续按键复用
        GetKeyboardState(keyboardState);
        charResult := GetCharFromVirtualKey(LKBDLLHOOKSTRUCT^.vkCode, LKBDLLHOOKSTRUCT^.scanCode, keyboardState);
        if charResult <> '' then
        begin
          if wParam = WM_DEADCHAR then
            Writeln('捕获死键: ', charResult)
          else
            Writeln('捕获字符: ', charResult);
        end;
      end;
    end;
  end;
end;

procedure InitHook;
begin
  hhk := SetWindowsHookEx(WH_KEYBOARD_LL, @LowLevelKeyboardProc, HInstance, 0);
  if hhk = 0 then RaiseLastOSError;
end;

procedure KillHook;
begin
  if (hhk <> 0) then UnhookWindowsHookEx(hhk);
end;

begin
  try
    InitHook;
    Writeln('钩子已启动,按任意键测试,按Ctrl+C退出...');
    while GetMessage(Msg, 0, 0, 0) do
    begin
      TranslateMessage(Msg);
      DispatchMessage(Msg);
    end;
  except
    on E: Exception do Writeln(E.ClassName, ': ', E.Message);
  end;
  KillHook;
end.

关键改动说明

  • 扫描码MSB设置:在GetCharFromVirtualKey中为扫描码添加最高有效位,这是让系统正确识别死键的核心操作,对应你提到的“执行最高有效位操作”要求。
  • 键盘状态复用:传递并保留键盘状态,确保死键的标记能被后续按键读取,生成带重音的最终字符(比如按下´后再按a,会输出á)。
  • 新增WM_DEADCHAR监听:这个消息专门用于通知死键按下事件,能更精准地捕获死键触发动作。
  • 移除冗余的ReverseBits:原代码中的位反转操作是多余的,ToAsciiEx直接使用带MSB的原始扫描码即可正常工作。

内容的提问来源于stack exchange,提问作者user13342561

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 18:42:38