c# 全局钩子实现扫码枪获取信息。

原文:c# 全局钩子实现扫码枪获取信息。

1.扫描枪获取数据原理基本相当于键盘数据,获取扫描枪扫描出来的数据,一般分为两种实现方式。

  a)文本框输入获取焦点,扫描后自动显示在文本框内。

  b)使用键盘钩子,勾取扫描枪虚拟按键,根据按键频率进行手动输入和扫描枪扫描判断。

2.要实现系统钩子其实很简单,调用三个Win32的API即可。

SetWindowsHookEx 用于设置钩子。(设立一道卡子,盘查需要的信息)

CallNextHookEx 用于传递钩子(消息是重要的,所以从哪里来,就应该回到哪里去,除非你决定要*消息)

UnhookWindowsHookEx 卸载钩子(卸载很重要,卡子设多了会造成拥堵)

版本一:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Diagnostics;
namespace SaomiaoTest2
{
/// <summary>
/// 获取键盘输入或者USB扫描枪数据 可以是没有焦点 应为使用的是全局钩子
/// USB扫描枪 是模拟键盘按下
/// 这里主要处理扫描枪的值,手动输入的值不太好处理
/// </summary>
public class BardCodeHooK
{
public delegate void BardCodeDeletegate(BarCodes barCode);
public event BardCodeDeletegate BarCodeEvent; //定义成静态,这样不会抛出回收异常
private static HookProc hookproc; public struct BarCodes
{
public int VirtKey;//虚拟吗
public int ScanCode;//扫描码
public string KeyName;//键名
public uint Ascll;//Ascll
public char Chr;//字符 public string BarCode;//条码信息 保存最终的条码
public bool IsValid;//条码是否有效
public DateTime Time;//扫描时间,
} private struct EventMsg
{
public int message;
public int paramL;
public int paramH;
public int Time;
public int hwnd;
} [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId); [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern bool UnhookWindowsHookEx(int idHook); [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam); [DllImport("user32", EntryPoint = "GetKeyNameText")]
private static extern int GetKeyNameText(int IParam, StringBuilder lpBuffer, int nSize); [DllImport("user32", EntryPoint = "GetKeyboardState")]
private static extern int GetKeyboardState(byte[] pbKeyState); [DllImport("user32", EntryPoint = "ToAscii")]
private static extern bool ToAscii(int VirtualKey, int ScanCode, byte[] lpKeySate, ref uint lpChar, int uFlags); [DllImport("kernel32.dll")]
public static extern IntPtr GetModuleHandle(string name); delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
BarCodes barCode = new BarCodes();
int hKeyboardHook = 0;
string strBarCode = ""; private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
{
if (nCode == 0)
{
EventMsg msg = (EventMsg)Marshal.PtrToStructure(lParam, typeof(EventMsg));
if (wParam == 0x100)//WM_KEYDOWN=0x100
{
barCode.VirtKey = msg.message & 0xff;//虚拟吗
barCode.ScanCode = msg.paramL & 0xff;//扫描码
StringBuilder strKeyName = new StringBuilder(225);
if (GetKeyNameText(barCode.ScanCode * 65536, strKeyName, 255) > 0)
{
barCode.KeyName = strKeyName.ToString().Trim(new char[] { ' ', '\0' });
}
else
{
barCode.KeyName = "";
}
byte[] kbArray = new byte[256];
uint uKey = 0;
GetKeyboardState(kbArray); if (ToAscii(barCode.VirtKey, barCode.ScanCode, kbArray, ref uKey, 0))
{
barCode.Ascll = uKey;
barCode.Chr = Convert.ToChar(uKey);
} TimeSpan ts = DateTime.Now.Subtract(barCode.Time); if (ts.TotalMilliseconds > 50)
{//时间戳,大于50 毫秒表示手动输入
strBarCode = barCode.Chr.ToString();
}
else
{
if ((msg.message & 0xff) == 13 && strBarCode.Length > 3)
{//回车
barCode.BarCode = strBarCode;
barCode.IsValid = true;
}
strBarCode += barCode.Chr.ToString();
}
barCode.Time = DateTime.Now;
if (BarCodeEvent != null)
BarCodeEvent(barCode);//触发事件
barCode.IsValid = false;
}
}
return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
} //安装钩子
public bool Start()
{
if (hKeyboardHook == 0)
{
hookproc = new HookProc(KeyboardHookProc); //GetModuleHandle 函数 替代 Marshal.GetHINSTANCE
//防止在 framework4.0中 注册钩子不成功
IntPtr modulePtr = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName); //WH_KEYBOARD_LL=13
//全局钩子 WH_KEYBOARD_LL
// hKeyboardHook = SetWindowsHookEx(13, hookproc, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0); hKeyboardHook = SetWindowsHookEx(13, hookproc, modulePtr, 0);
}
return (hKeyboardHook != 0);
} //卸载钩子
public bool Stop()
{
if (hKeyboardHook != 0)
{
return UnhookWindowsHookEx(hKeyboardHook);
}
return true;
}
}
}

在实践过程中,发现版本一的代码只能扫描条形码,如伴随二维码中的字母出现就不能正确获取数据。

版本二:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text; namespace BarcodeScanner
{
public class ScanerHook
{
public delegate void ScanerDelegate(ScanerCodes codes);
public event ScanerDelegate ScanerEvent;

      //private const int WM_KEYDOWN = 0x100;//KEYDOWN
      //private const int WM_KEYUP = 0x101;//KEYUP
      //private const int WM_SYSKEYDOWN = 0x104;//SYSKEYDOWN
      //private const int WM_SYSKEYUP = 0x105;//SYSKEYUP

//private static int HookProc(int nCode, Int32 wParam, IntPtr lParam);
private int hKeyboardHook = 0;//声明键盘钩子处理的初始值
private ScanerCodes codes = new ScanerCodes();//13为键盘钩子
//定义成静态,这样不会抛出回收异常
private static HookProc hookproc;
delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
     //设置钩子
private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
     //卸载钩子
private static extern bool UnhookWindowsHookEx(int idHook);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
//继续下个钩子
private static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam); [DllImport("user32", EntryPoint = "GetKeyNameText")]
private static extern int GetKeyNameText(int IParam, StringBuilder lpBuffer, int nSize);
[DllImport("user32", EntryPoint = "GetKeyboardState")]
     //获取按键的状态
private static extern int GetKeyboardState(byte[] pbKeyState);
[DllImport("user32", EntryPoint = "ToAscii")]
     //ToAscii职能的转换指定的虚拟键码和键盘状态的相应字符或字符
private static extern bool ToAscii(int VirtualKey, int ScanCode, byte[] lpKeySate, ref uint lpChar, int uFlags);
    

      //int VirtualKey //[in] 指定虚拟关键代码进行翻译。
      //int uScanCode, // [in] 指定的硬件扫描码的关键须翻译成英文。高阶位的这个值设定的关键,如果是(不压)
      //byte[] lpbKeyState, // [in] 指针,以256字节数组,包含当前键盘的状态。每个元素(字节)的数组包含状态的一个关键。如果高阶位的字节是一套,关键是下跌(按下)。在低比特,如/果设置表明,关键是对切换。在此功能,只有肘位的CAPS LOCK键是相关的。在切换状态的NUM个锁和滚动锁定键被忽略。
      //byte[] lpwTransKey, // [out] 指针的缓冲区收到翻译字符或字符。
      //uint fuState); // [in] Specifies whether a menu is active. This parameter must be 1 if a menu is active, or 0 otherwise.


        

            
        [DllImport("kernel32.dll")]
     //使用WINDOWS API函数代替获取当前实例的函数,防止钩子失效
public static extern IntPtr GetModuleHandle(string name);
public ScanerHook()
{
}
public bool Start()
{
if (hKeyboardHook == 0)
{
hookproc = new HookProc(KeyboardHookProc);
//GetModuleHandle 函数 替代 Marshal.GetHINSTANCE
//防止在 framework4.0中 注册钩子不成功
IntPtr modulePtr = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);
//WH_KEYBOARD_LL=13
//全局钩子 WH_KEYBOARD_LL
// hKeyboardHook = SetWindowsHookEx(13, hookproc, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0);
hKeyboardHook = SetWindowsHookEx(13, hookproc, modulePtr, 0);
}
return (hKeyboardHook != 0);
}
public bool Stop()
{
if (hKeyboardHook != 0)
{
bool retKeyboard = UnhookWindowsHookEx(hKeyboardHook);
hKeyboardHook = 0;
return retKeyboard; }
return true;
}
private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
{ EventMsg msg = (EventMsg)Marshal.PtrToStructure(lParam, typeof(EventMsg));
codes.Add(msg);
if (ScanerEvent != null && msg.message == 13 && msg.paramH > 0 && !string.IsNullOrEmpty(codes.Result))
{
ScanerEvent(codes);
}
return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
}
public class ScanerCodes
{
private int ts = 100; // 指定输入间隔为300毫秒以内时为连续输入
private List<List<EventMsg>> _keys = new List<List<EventMsg>>();
private List<int> _keydown = new List<int>(); // 保存组合键状态
private List<string> _result = new List<string>(); // 返回结果集
private DateTime _last = DateTime.Now;
private byte[] _state = new byte[256];
private string _key = string.Empty;
private string _cur = string.Empty;
public EventMsg Event
{
get
{
if (_keys.Count == 0)
{
return new EventMsg();
}
else
{
return _keys[_keys.Count - 1][_keys[_keys.Count - 1].Count - 1];
}
}
}
public List<int> KeyDowns
{
get
{
return _keydown;
}
}
public DateTime LastInput
{
get
{
return _last;
}
}
public byte[] KeyboardState
{
get
{
return _state;
}
}
public int KeyDownCount
{
get
{
return _keydown.Count;
}
}
public string Result
{
get
{
if (_result.Count > 0)
{
return _result[_result.Count - 1].Trim();
}
else
{
return null;
}
}
}
public string CurrentKey
{
get
{
return _key;
}
}
public string CurrentChar
{
get
{
return _cur;
}
}
public bool isShift
{
get
{
return _keydown.Contains(160);
}
}
public void Add(EventMsg msg)
{
#region 记录按键信息 // 首次按下按键
if (_keys.Count == 0)
{
_keys.Add(new List<EventMsg>());
_keys[0].Add(msg);
_result.Add(string.Empty);
}
// 未释放其他按键时按下按键
else if (_keydown.Count > 0)
{
_keys[_keys.Count - 1].Add(msg);
}
// 单位时间内按下按键
else if (((TimeSpan)(DateTime.Now - _last)).TotalMilliseconds < ts)
{
_keys[_keys.Count - 1].Add(msg);
}
// 从新记录输入内容
else
{
_keys.Add(new List<EventMsg>());
_keys[_keys.Count - 1].Add(msg);
_result.Add(string.Empty);
}
#endregion
_last = DateTime.Now;
#region 获取键盘状态
// 记录正在按下的按键
if (msg.paramH == 0 && !_keydown.Contains(msg.message))
{
_keydown.Add(msg.message);
}
// 清除已松开的按键
if (msg.paramH > 0 && _keydown.Contains(msg.message))
{
_keydown.Remove(msg.message);
}
#endregion
#region 计算按键信息 int v = msg.message & 0xff;
int c = msg.paramL & 0xff;
StringBuilder strKeyName = new StringBuilder(500);
if (GetKeyNameText(c * 65536, strKeyName, 255) > 0)
{
_key = strKeyName.ToString().Trim(new char[] { ' ', '\0' });
GetKeyboardState(_state);
if (_key.Length == 1 && msg.paramH == 0)// && msg.paramH == 0
{
// 根据键盘状态和shift缓存判断输出字符
_cur = ShiftChar(_key, isShift, _state).ToString();
_result[_result.Count - 1] += _cur;
}
              // 备选
           else
{
_cur = string.Empty;
}
}
#endregion
}
private char ShiftChar(string k, bool isShiftDown, byte[] state)
{
bool capslock = state[0x14] == 1;
bool numlock = state[0x90] == 1;
bool scrolllock = state[0x91] == 1;
bool shiftdown = state[0xa0] == 1;
char chr = (capslock ? k.ToUpper() : k.ToLower()).ToCharArray()[0];
if (isShiftDown)
{
if (chr >= 'a' && chr <= 'z')
{
chr = (char)((int)chr - 32);
}
else if (chr >= 'A' && chr <= 'Z')
{
if (chr=='Z')
{
string s = "";
}
chr = (char)((int)chr + 32);
}
else
{
string s = "`1234567890-=[];',./";
string u = "~!@#$%^&*()_+{}:\"<>?";
if (s.IndexOf(chr) >= 0)
{
return (u.ToCharArray())[s.IndexOf(chr)];
}
}
}
return chr;
}
}
public struct EventMsg
{
public int message;
public int paramL;
public int paramH;
public int Time;
public int hwnd;
}
}
}

版本二中的代码,实践中发现出现了获取扫描数据却省略“+”加号的情况出现。

因此在版本二中备选处添加

             //判断是+ 强制添加+
else if (_key.Length == 5 && msg.paramH == 0&&msg.paramL==78&&msg.message==107)// && msg.paramH == 0
{
// 根据键盘状态和shift缓存判断输出字符
_cur = Convert.ToChar('+').ToString();
_result[_result.Count - 1] += _cur;
}

3.winform在无焦点情况下的使用方式:

using BarcodeScanner;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms; namespace BarCodeTest
{
public partial class Form1 : Form
{
private ScanerHook listener = new ScanerHook();
public Form1()
{
InitializeComponent();
listener.ScanerEvent += Listener_ScanerEvent;
}
private void Listener_ScanerEvent(ScanerHook.ScanerCodes codes)
{
textBox3.Text = codes.Result;
}
private void Form1_Load(object sender, EventArgs e)
{
listener.Start();
} private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
listener.Stop();
}
}
}
上一篇:iOS 发布项目到CocoaPods其实没那么复杂????


下一篇:nullcon HackIM 2016 -- Crypto Question 5