【问题标题】:Double characters shown when typing special characters while logging keystrokes in c#在 c# 中记录击键时键入特殊字符时显示双字符
【发布时间】:2013-03-18 23:09:50
【问题描述】:

我有一个应用程序可以记录用户按下的任何内容,但是当我按下特殊字符(如´a)来获取á 时,我得到´´a;当我想得到à 时,同样的事情,然后我得到``a,所以所有特殊字符都输入两次,然后输入常规字符。

我一直在寻找,但真的找不到任何东西。但我注意到问题出在ToAscii 方法中,没有正确输入字符。

public string GetString(IntPtr lParam, int vCode)
{
    try
    {
        bool shift = Keys.Shift == Control.ModifierKeys || Console.CapsLock;

        string value = ""; 

        KeyboardHookStruct MyKeyboardHookStruct = 
            (KeyboardHookStruct)Marshal.PtrToStructure(
                lParam, typeof(KeyboardHookStruct));

        byte[] keyState = new byte[256];
        byte[] inBuffer = new byte[2];

        DllClass.GetKeyboardState(keyState);

        var ascii=
            DllClass.ToAscii(
                MyKeyboardHookStruct.vkCode, 
                MyKeyboardHookStruct.scanCode, 
                keyState, inBuffer, MyKeyboardHookStruct.flags
                );

        if (ascii == 1)
        {
            char key = (char)inBuffer[0];

            if ((shift) && Char.IsLetter(key))
                key = Char.ToUpper(key);

            value = key.ToString();
        }

        return value;
    }
    catch (Exception)
    {
        return "";
    }
}

我是否遗漏了什么或做错了什么?所有其他字符都可以正常工作,但特殊字符以双字符形式出现。


编辑:

尝试改用ToUnicode

[DllImport("USER32.DLL", CharSet = CharSet.Unicode)]
public static extern int ToUnicode(
    uint virtualKey, uint scanCode, byte[] keyStates, 
    [MarshalAs(UnmanagedType.LPArray)] [Out] char[] chars, 
    int charMaxCount, uint flags);

public string GetString(IntPtr lParam, int vCode)
{
    try
    {
        bool shift = Keys.Shift == Control.ModifierKeys || Console.CapsLock;

        string value = ""; 

        KeyboardHookStruct MyKeyboardHookStruct = 
            (KeyboardHookStruct)Marshal.PtrToStructure(
                lParam, typeof(KeyboardHookStruct));

        byte[] keyState = new byte[256];
        byte[] inBuffer = new byte[2];

        char[] chars = new char[2];

        DllClass.GetKeyboardState(keyState);

        int val = 0;

        val = ToUnicode(
                (uint)MyKeyboardHookStruct.vkCode, 
                (uint)MyKeyboardHookStruct.scanCode, 
                keyState, chars, chars.Length, 0
                );

        val = ToUnicode(
                (uint)MyKeyboardHookStruct.vkCode, 
                (uint)MyKeyboardHookStruct.scanCode, 
                keyState, chars, chars.Length, 0
                );

        if (val == 1)
        {
            char key = (char)chars[0];

            if ((shift) && Char.IsLetter(key))
                key = Char.ToUpper(key);

            value = key.ToString();
        }

        return value;
    }
    catch (Exception)
    {
        return "";
    }
}

请有人帮助我,我真的需要解决这个问题=/


编辑:

int val = -1;

if (IsDeadKey((uint)vCode))
{
    while (val == -1)
    {
        val = ToUnicode(
                (uint)MyKeyboardHookStruct.vkCode, 
                (uint)MyKeyboardHookStruct.scanCode, 
                keyState, chars, chars.Length, 0
                );
    }
}
else
    val = ToUnicode(
            (uint)MyKeyboardHookStruct.vkCode, 
            (uint)MyKeyboardHookStruct.scanCode, 
            keyState, chars, chars.Length, 0
            );

所以现在我尝试调用ToAsciiToUnicode 几次来刷新真实角色,但没有成功。我做错了吗?

和ASCII一样,首先调用´我得到-1,所以我再次调用它,然后我得到1;然后我按a,得到á,但我只得到a。同样的事情,如果我连续两次使用ToUnicode,我只会得到a 而不是á,依此类推......

【问题讨论】:

  • 键盘记录器...它们甚至不应该存在。
  • 我完全同意我的朋友,但这不是你想象的那种键盘记录器。这实际上是一个学校项目,供那些难以记住用户名/密码的人使用,使用此工具,他们可以将所有内容保存在安全的地方。
  • 是的,键盘记录器和密码没有什么不安全的。
  • @syncis:现在你可以让它工作了。
  • @KenKin 其实没有,我没有 =/

标签: c# winforms logging character-encoding keycode


【解决方案1】:

但我注意到问题出在 ToAsciii 方法中,没有正确输入字符。

这正是我即将猜测的。我很感激你为我完成了腿部工作! :-)

问题在于这些“特殊”字符不是 ASCII 字符。也就是说,它们实际上是某种不属于 ASCII 字符集的花哨的 Unicode 字符。

当您尝试将它们转换为 ASCII 字符时,该函数可能会尽其所能,将构成 á 的代码点分解为单独的字符 ´a

显然这不是你想要的。您想将á 视为单个字符,因此您需要使用Unicode。这不是一个真正的问题:Windows 在内部使用 Unicode 至少十年了。抛弃陈旧的ToAscii 功能;相反,您需要使用MapVirtualKeyMapVirtualKeyEx 将您通过低级键盘挂钩获得的虚拟键代码转换为字符值。

【讨论】:

  • 非常感谢您提供此信息,但我有 1 个问题。还有一种类似于 ToAsciii 的方法,名为 ToUnicode,不能这样还是我应该尝试使用 mapvirtualkey?
  • @syncis 是的,ToUnicode/ToUnicodeEx 可能会有一些运气,但这两种方法仍然存在死键问题。一种可能的解决方法是,如果您第一次获得死键(返回值为 -1),则再次调用它。相关阅读:blogs.msdn.com/b/michkap/archive/2005/01/19/355870.aspx
  • 好的,让我们尝试第一种方法,调用它们两次,我首先用 ToAscii 调用它,如果它返回 -1,我再调用一次,对吗?现在尝试,我没有得到双字符但我没有得到'超过a,我只是得到a,而不是á。也许 mapvirtualkeyex 可以解决这个问题?
  • ToUnicode 会解决这个问题。您对ToAscii 的问题仍然是á 不是ASCII 字符。
  • 您的代码仍然不检查返回值,它只是盲目地调用该函数两次。我强烈建议阅读链接的文档。
【解决方案2】:
  • 关于ToAsciiToUnicode的神话

    在问题中,您提到您已经尝试了ToAsciiToUnicode,但均未成功。我还搜索了一个相对的问题:

    ToAscii/ToUnicode in a keyboard hook destroys dead keys

    我并不是说任何答案是对还是错。但是,我们可以考虑:

    • 一个(不那么)棘手的游戏

      在这个游戏中,我一次给玩家一个随机翻转的 50 美分硬币。如果谁收集了一对50美分的硬币,一个是正面另一个是反面,可以挑战从我这里得到一美元。

      如果一个人放弃挑战,那么谁能保留那 50 美分,然后游戏重新开始。如果谁尝试但没有收集到两个符合规则,那么我要求将我给的那些归还给我。

      如何从我这里得到一美元的钞票而不会丢失任何硬币?

    也许是穿越时空..


基于 CodeProject 上的 [Processing Global Mouse and Keyboard Hooks in C#]

还有我很老的答案:Konami Code in C#

为了回答你的问题,我做了一些修改。

  • 代码

    namespace Gma.UserActivityMonitor {
        using System.Diagnostics;
        using System.Windows.Forms;
    
        using System.Collections.Generic;
        using System.Linq;
    
        partial class HookManager {
            private static int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam) {
                // indicates if any of underlaing events set e.Handled flag
                bool handled=false;
    
                if(nCode>=0) {
                    // read structure KeyboardHookStruct at lParam
                    var MyKeyboardHookStruct=
                        (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
    
                    // raise KeyDown
                    if(s_KeyDown!=null&&(wParam==WM_KEYDOWN||wParam==WM_SYSKEYDOWN)) {
                        Keys keyData=(Keys)MyKeyboardHookStruct.VirtualKeyCode;
                        KeyEventArgs e=new KeyEventArgs(keyData);
                        s_KeyDown.Invoke(null, e);
                        handled=e.Handled;
                    }
    
                    // raise KeyPress
                    if(s_KeyPress!=null&&wParam==WM_KEYDOWN) {
                        var keyText=GetString(lParam, nCode, ref handled);
    
                        if(""!=keyText) {
                            var keyChar=keyText.First();
                            Debug.Print("keyText => {0}", keyText);
    
    #if false
                            if(AccentFormatter.Combination.Values.Contains(keyChar)) {
                                SendKeys.Send("\b"+keyText);
                                return -1;
                            }
    #endif
                        }
                    }
    
                    // raise KeyUp
                    if(s_KeyUp!=null&&(wParam==WM_KEYUP||wParam==WM_SYSKEYUP)) {
                        Keys keyData=(Keys)MyKeyboardHookStruct.VirtualKeyCode;
                        KeyEventArgs e=new KeyEventArgs(keyData);
                        s_KeyUp.Invoke(null, e);
                        handled=handled||e.Handled;
                    }
                }
    
                // if event handled in application do not handoff to other listeners
                if(handled)
                    return -1;
    
                // forward to other application
                return CallNextHookEx(s_KeyboardHookHandle, nCode, wParam, lParam);
            }
    
            public static String GetString(IntPtr lParam, int vCode, ref bool handled) {
                var MyKeyboardHookStruct=
                    (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
    
                bool isDownShift=((GetKeyState(VK_SHIFT)&0x80)==0x80?true:false);
                bool isDownCapslock=(GetKeyState(VK_CAPITAL)!=0?true:false);
    
                byte[] keyState=new byte[256];
                GetKeyboardState(keyState);
                byte[] inBuffer=new byte[2];
    
                var keyText="";
    
                var ascii=
                    ToAscii(
                        MyKeyboardHookStruct.VirtualKeyCode,
                        MyKeyboardHookStruct.ScanCode,
                        keyState, inBuffer, MyKeyboardHookStruct.Flags
                        );
    
                if(ascii==1) {
                    char key=(char)inBuffer[0];
    
                    if((isDownCapslock^isDownShift)&&Char.IsLetter(key))
                        key=Char.ToUpper(key);
    
                    KeyPressEventArgs e=new KeyPressEventArgs(key);
                    s_KeyPress.Invoke(null, e);
                    handled=handled||e.Handled;
    
                    keyText=new String(new[] { e.KeyChar });
                    var sequence=KeySequence.Captured(e.KeyChar);
    
                    if(null!=sequence)
                        keyText=sequence.ToString(AccentFormatter.Default);
                }
    
                return keyText;
            }
        }
    
        public class KeySequence {
            public String ToString(IFormatProvider provider) {
                return
                    null==provider
                        ?new String(Sequence.Select(x => (char)x).ToArray())
                        :String.Format(provider, "{0}", Sequence);
            }
    
            public override String ToString() {
                return this.ToString(default(IFormatProvider));
            }
    
            public bool Captures(int keyValue) {
                for(var i=Sequence.Length; i-->0; ) {
                    if(Sequence[i]!=keyValue) {
                        if(0==i)
                            Count=0;
    
                        continue;
                    }
    
                    if(Count!=i)
                        continue;
    
                    ++Count;
                    break;
                }
    
                var x=Sequence.Length==Count;
                Count=x?0:Count;
                return x;
            }
    
            public KeySequence(int[] newSequence) {
                Sequence=newSequence;
            }
    
            public static KeySequence Captured(int keyValue) {
                return m_List.FirstOrDefault(x => x.Captures(keyValue));
            }
    
            public int Count {
                private set;
                get;
            }
    
            public int[] Sequence {
                set;
                get;
            }
    
            static KeySequence() {
                m_List.AddRange(
                    from x in AccentFormatter.Combination.Keys
                    let intArray=x.Select(c => (int)c).ToArray()
                    select new KeySequence(intArray)
                    );
            }
    
            static readonly List<KeySequence> m_List=new List<KeySequence>();
        }
    
        public class AccentFormatter: IFormatProvider, ICustomFormatter {
            String ICustomFormatter.Format(String format, object arg, IFormatProvider formatProvider) {
                return GetAccent(new String((arg as int[]).Select(x => (char)x).ToArray()));
            }
    
            object IFormatProvider.GetFormat(Type formatType) {
                return typeof(ICustomFormatter)!=formatType?null:this;
            }
    
            public static String GetAccent(String input) {
                return
                    Combination.Keys.Contains(input, StringComparer.OrdinalIgnoreCase)
                        ?Combination[input].ToString()
                        :"";
            }
    
            static AccentFormatter() {
                AcuteSymbol=((char)0xb4).ToString();
                GraveSymbol=('`').ToString();
    
                var ae=(char)0xe6;
                var oe=(char)0xf8;
                AcuteCandidates="acegiklmnoprsuwyz".ToArray().Concat(new[] { ae, oe }).ToArray();
                GraveCandidates="aeinouwy".ToArray();
    
                var lowerAcuteAccents=(
                    new[] { 
                        0xe1, 0x107, 
                        0xe9, 0x1f5, 
                        0xed, 0x1e31, 0x13a, 0x1e3f, 0x144, 
                        0xf3, 0x1e55, 0x155, 0x15b, 
                        0xfa, 0x1e83, 0xfd, 0x17a, 
                        0x1fd, 0x1ff
                    }
                    ).Select(
                        (x, i) => new {
                            Key=AcuteSymbol+AcuteCandidates[i],
                            Value=(char)x
                        }
                        );
    
                var upperAcuteAccents=(
                    new[] { 
                        0xc1, 0x106, 
                        0xc9, 0x1f4, 
                        0xcd, 0x1e30, 0x139, 0x1e3e, 0x143, 
                        0xd3, 0x1e54, 0x154, 0x15a, 
                        0xda, 0x1e82, 0xdd, 0x179, 
                        0x1fc, 0x1fe
                    }
                    ).Select(
                        (x, i) => new {
                            Key=AcuteSymbol+char.ToUpper(AcuteCandidates[i]),
                            Value=(char)x
                        }
                        );
    
                var lowerGraveAccents=(
                    new[] { 0xe0, 0xe8, 0xec, 0x1f9, 0xf2, 0xf9, 0x1e81, 0x1ef3 }
                    ).Select(
                        (x, i) => new {
                            Key=GraveSymbol+GraveCandidates[i],
                            Value=(char)x
                        }
                        );
    
                var upperGraveAccents=(
                    new[] { 0xc0, 0xc8, 0xcc, 0x1f8, 0xd2, 0xd9, 0x1e80, 0x1ef2 }
                    ).Select(
                        (x, i) => new {
                            Key=GraveSymbol+char.ToUpper(GraveCandidates[i]),
                            Value=(char)x
                        }
                        );
    
                Combination=
                    lowerAcuteAccents
                        .Concat(upperAcuteAccents)
                        .Concat(lowerGraveAccents)
                        .Concat(upperGraveAccents)
                        .ToDictionary(x => x.Key, x => x.Value);
            }
    
            public static readonly Dictionary<String, char> Combination;
            public static readonly String AcuteSymbol, GraveSymbol;
            public static readonly char[] AcuteCandidates, GraveCandidates;
            public static readonly AccentFormatter Default=new AccentFormatter();
        }
    }
    

    首先,使用从 CodeProject 下载的代码,找到 HookManager.Callbacks.cs删除整个方法 KeyboardHookProc

    然后您可以将上面的代码保存在一个新文件中,例如HookManager.Modified.cs 并将其添加到项目Gma.UserActivityMonitor 中,或者将其粘贴到HookManager.Callbacks.cs 的后面。

    记得将Gma.UserActivityMonitorDemo设置为启动项目。将目标框架设置为 2.0,您可能希望设置为更高。

  • 输入→输出

  • 关于口音

    当我搜索时,有两种一般口音,它们是grave accentacute accent
    重音的变音符号是´,可能的字母是áǽćéǵíḱĺḿńóǿṕŕśúẃýź
    重音的变音符号是``, and the possible letters areàèìǹòùẁỳ`。

    它们都可以大写或小写,但我没有找到一种足够简单的方法在框架内置的类中使用它们。例如,我不能使用ToUpperToLower 来获得与Ǹǹ 的相反情况,我什至尝试过ToUpperInvariantToLowerInvariant。因此,我选择在AccentFormatter 中使用硬编码序列,您可以将其更改为从文件中读取,或者自己实现另一个自定义格式化程序。

  • 十六进制表示的数组排列

    在代码中,我将重音序列排列为:

    0xc1, 0x106, 0xc9, 0x1f4, 0xcd, 0x1e30, 0x139, 0x1e3e, 0x143, 0xd3, 0x1e54, 0x154, 0x15a, 0xda, 0x1e82, 0xdd, 0x179, 0x1fc, 0x1fe

    即:

    阿克 ÉǴ ÍḰĹḾŃ ÓṔŔŚ ÚẂÝŹ ǼǾ

    ǼǾ 放在后面,因为ÆØ 在 ASCII(非扩展)中没有对应的字符。

    我更喜欢使用默认代码页将代码保存在 ANSI 中,因为我的 CultureInfo 与字母表中的语言不同。您可能希望将它们更改为精确的字符以提高可读性。

  • 将密钥发送到活动应用程序

    如果您想将密钥发送到活动应用程序,请在代码中进行以下更改:

    改变

    #if false
    

    #if !false
    

    在条件编译块里面,代码片段是

    if(AccentFormatter.Combination.Values.Contains(keyChar)) {
        SendKeys.Send("\b"+keyText);
        return -1;
    }
    

    一旦识别为特定组合,它会使用退格字符删除先前键入的 ``or´`。这里的退格是时间机器..

在这个演示之后,我想你会知道如何修改它们以合并到你的代码中。

【讨论】:

  • +1 感谢分享。我做了很多研究,发现很多人都陷入了困境。
  • Í 不能让它工作,仍然得到没有 á 的 a。我做错了什么,我正在使用您提到的确切项目并将两个项目升级到 4.0,但它们仍然无法工作。
  • 是的,我想我现在明白了你的想法,非常好,我的朋友,非常好!我认为实际上我可以通过我的应用程序来完成这项工作,因为我现在收到了 á。
  • @JeremyThompson:谢谢。但我不确定坚持这一点是指全局钩子还是捕获特定键序列 ..
  • 我现在注意到了同样的问题,它可以在应用程序中运行,但是当我在记事本中记录和写入时,我会得到 ´´a ... 回到从头开始 1 :( 也许将 ToAscii 更改为 ToUnicode会工作吗?
猜你喜欢
  • 2011-05-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多