抓取掃描槍掃描數據的案例


背景:

    最近要做一個抓取掃描槍掃描條形碼獲取條形碼數據的功能,以前沒有玩過掃描槍,但是因為做過很多其他方面的外設獲取數據的項目,所以原理也明白,都是相當於鍵盤輸入,所以相當的是通過獲取鍵盤輸入的方案實現,因為這個功能點是用於整個pc上所有掃描槍程序的數據的抓取,就是其他程序用掃描槍,我做的這個程序也能抓取到數據,並對數據進行相應的處理,至於數據處理那是后面自己所做的業務需求的處理了,和抓取掃描槍掃描數據無關了,所以可以通過全局鍵盤鈎子抓取鍵盤的輸入去實現,這樣能夠去獲取每個鍵盤輸入的值,想法有了,那就是實現功能了,至於代碼方面嗎!由於鄙人做的是Windows客戶端的功能,熟悉C#,所以下面的演示是通過WPF展示。

Solution:

   有了想法肯定要實際去操作了,首先知道通過全局鍵盤鈎子去抓取鍵盤輸入的數據,那就調用Win32Api創建鍵盤鈎子Hook去實現,這個功能實現代碼在網上一搜,那是太容易找到了,博客園里面也有,但是一個完整的功能自己還是自己Copy一下吧!里面注釋很全面,都能看懂!

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Runtime.InteropServices;
  5 using System.Text;
  6 using System.Threading.Tasks;
  7 using System.Windows.Forms;
  8 
  9 namespace MedicalMain
 10 {
 11     public class KeyboardHook
 12     {
 13         public event KeyEventHandler KeyDownEvent;
 14         public event KeyPressEventHandler KeyPressEvent;
 15         public event KeyEventHandler KeyUpEvent;
 16 
 17         public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
 18         static int hKeyboardHook = 0; //聲明鍵盤鈎子處理的初始值
 19         //值在Microsoft SDK的Winuser.h里查詢
 20         public const int WH_KEYBOARD_LL = 13;   //線程鍵盤鈎子監聽鍵盤消息設為2,全局鍵盤監聽鼠標消息設為13
 21         HookProc KeyboardHookProcedure; //聲明KeyboardHookProcedure作為HookProc類型
 22         //鍵盤結構 
 23         [StructLayout(LayoutKind.Sequential)]
 24         public class KeyboardHookStruct
 25         {
 26             public int vkCode;  //定一個虛擬鍵碼。該代碼必須有一個價值的范圍1至254
 27             public int scanCode; // 指定的硬件掃描碼的關鍵
 28             public int flags;  // 鍵標志
 29             public int time; // 指定的時間戳記的這個訊息
 30             public int dwExtraInfo; // 指定額外信息相關的信息
 31         }
 32         //使用此功能,安裝了一個鈎子
 33         [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
 34         public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
 35 
 36 
 37         //調用此函數卸載鈎子
 38         [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
 39         public static extern bool UnhookWindowsHookEx(int idHook);
 40 
 41 
 42         //使用此功能,通過信息鈎子繼續下一個鈎子
 43         [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
 44         public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam);
 45 
 46         // 取得當前線程編號(線程鈎子需要用到) 
 47         [DllImport("kernel32.dll")]
 48         static extern int GetCurrentThreadId();
 49 
 50         //使用WINDOWS API函數代替獲取當前實例的函數,防止鈎子失效
 51         [DllImport("kernel32.dll")]
 52         public static extern IntPtr GetModuleHandle(string name);
 53 
 54         public void Start()
 55         {
 56             // 安裝鍵盤鈎子
 57             if (hKeyboardHook == 0)
 58             {
 59                 KeyboardHookProcedure = new HookProc(KeyboardHookProc);
 60                 hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, GetModuleHandle(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName), 0);
 61                 //hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0);
 62                 //************************************ 
 63                 //鍵盤線程鈎子 
 64                 //SetWindowsHookEx( 2,KeyboardHookProcedure, IntPtr.Zero, GetCurrentThreadId());//指定要監聽的線程idGetCurrentThreadId(),
 65                 //鍵盤全局鈎子,需要引用空間(using System.Reflection;) 
 66                 //SetWindowsHookEx( 13,MouseHookProcedure,Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]),0); 
 67                 // 
 68                 //關於SetWindowsHookEx (int idHook, HookProc lpfn, IntPtr hInstance, int threadId)函數將鈎子加入到鈎子鏈表中,說明一下四個參數: 
 69                 //idHook 鈎子類型,即確定鈎子監聽何種消息,上面的代碼中設為2,即監聽鍵盤消息並且是線程鈎子,如果是全局鈎子監聽鍵盤消息應設為13, 
 70                 //線程鈎子監聽鼠標消息設為7,全局鈎子監聽鼠標消息設為14。lpfn 鈎子子程的地址指針。如果dwThreadId參數為0 或是一個由別的進程創建的 
 71                 //線程的標識,lpfn必須指向DLL中的鈎子子程。 除此以外,lpfn可以指向當前進程的一段鈎子子程代碼。鈎子函數的入口地址,當鈎子鈎到任何 
 72                 //消息后便調用這個函數。hInstance應用程序實例的句柄。標識包含lpfn所指的子程的DLL。如果threadId 標識當前進程創建的一個線程,而且子 
 73                 //程代碼位於當前進程,hInstance必須為NULL。可以很簡單的設定其為本應用程序的實例句柄。threaded 與安裝的鈎子子程相關聯的線程的標識符
 74                 //如果為0,鈎子子程與所有的線程關聯,即為全局鈎子
 75                 //************************************ 
 76                 //如果SetWindowsHookEx失敗
 77                 if (hKeyboardHook == 0)
 78                 {
 79                     Stop();
 80                     throw new Exception("安裝鍵盤鈎子失敗");
 81                 }
 82             }
 83         }
 84         public void Stop()
 85         {
 86             bool retKeyboard = true;
 87 
 88 
 89             if (hKeyboardHook != 0)
 90             {
 91                 retKeyboard = UnhookWindowsHookEx(hKeyboardHook);
 92                 hKeyboardHook = 0;
 93             }
 94 
 95             if (!(retKeyboard)) throw new Exception("卸載鈎子失敗!");
 96         }
 97         //ToAscii職能的轉換指定的虛擬鍵碼和鍵盤狀態的相應字符或字符
 98         [DllImport("user32")]
 99         public static extern int ToAscii(int uVirtKey, //[in] 指定虛擬關鍵代碼進行翻譯。 
100                                          int uScanCode, // [in] 指定的硬件掃描碼的關鍵須翻譯成英文。高階位的這個值設定的關鍵,如果是(不壓)
101                                          byte[] lpbKeyState, // [in] 指針,以256字節數組,包含當前鍵盤的狀態。每個元素(字節)的數組包含狀態的一個關鍵。如果高階位的字節是一套,關鍵是下跌(按下)。在低比特,如果設置表明,關鍵是對切換。在此功能,只有肘位的CAPS LOCK鍵是相關的。在切換狀態的NUM個鎖和滾動鎖定鍵被忽略。
102                                          byte[] lpwTransKey, // [out] 指針的緩沖區收到翻譯字符或字符。 
103                                          int fuState); // [in] Specifies whether a menu is active. This parameter must be 1 if a menu is active, or 0 otherwise. 
104 
105         //獲取按鍵的狀態
106         [DllImport("user32")]
107         public static extern int GetKeyboardState(byte[] pbKeyState);
108 
109 
110         [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
111         private static extern short GetKeyState(int vKey);
112 
113         private const int WM_KEYDOWN = 0x100;//KEYDOWN 
114         private const int WM_KEYUP = 0x101;//KEYUP
115         private const int WM_SYSKEYDOWN = 0x104;//SYSKEYDOWN
116         private const int WM_SYSKEYUP = 0x105;//SYSKEYUP
117 
118         private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
119         {
120             // 偵聽鍵盤事件
121             if ((nCode >= 0) && (KeyDownEvent != null || KeyUpEvent != null || KeyPressEvent != null))
122             {
123                 KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
124                 // raise KeyDown
125                 if (KeyDownEvent != null && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN))
126                 {
127                     Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
128                     KeyEventArgs e = new KeyEventArgs(keyData);
129                     KeyDownEvent(this, e);
130                 }
131 
132                 //鍵盤按下
133                 if (KeyPressEvent != null && wParam == WM_KEYDOWN)
134                 {
135                     byte[] keyState = new byte[256];
136                     GetKeyboardState(keyState);
137 
138                     byte[] inBuffer = new byte[2];
139                     if (ToAscii(MyKeyboardHookStruct.vkCode, MyKeyboardHookStruct.scanCode, keyState, inBuffer, MyKeyboardHookStruct.flags) == 1)
140                     {
141                         KeyPressEventArgs e = new KeyPressEventArgs((char)inBuffer[0]);
142                         KeyPressEvent(this, e);
143                     }
144                 }
145 
146                 // 鍵盤抬起 
147                 if (KeyUpEvent != null && (wParam == WM_KEYUP || wParam == WM_SYSKEYUP))
148                 {
149                     Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
150                     KeyEventArgs e = new KeyEventArgs(keyData);
151                     KeyUpEvent(this, e);
152                 }
153 
154             }
155             //如果返回1,則結束消息,這個消息到此為止,不再傳遞。
156             //如果返回0或調用CallNextHookEx函數則消息出了這個鈎子繼續往下傳遞,也就是傳給消息真正的接受者 
157             return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
158         }
159 
160         ~KeyboardHook()
161         {
162             Stop();
163         }
164 
165     }
166 }

     鍵盤鈎子功能類實現了,下面就是調用該類了,能夠抓取到鍵盤的輸入的每個值了

eyboardHook k_hook = new KeyboardHook();
  k_hook.KeyDownEvent += new System.Windows.Forms.KeyEventHandler(hook_KeyDown);//鈎住鍵按下
private void hook_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
///
}

      hook_KeyDown方法在每次按鍵都會觸發,能夠通過參數e獲取到每次鍵盤輸入的鍵值,這樣鍵盤鈎子雖然實現了,但是我們在正常的使用過程中有的時候是通過真實的鍵盤按鍵輸入,有的時候通過掃描槍掃描輸入,那這個時候該如何區分是通過掃描槍還是通過鍵盤按鍵輸入呢!后來通過度娘查了一下發現了一種解決方案,但是這個解決方案是有判斷不是非常精確的,通過按鍵的間隔時間去判斷去,下面代碼的實現:

   private StringBuilder inputKey=new StringBuilder();
           /// <summary>
 2         ///鍵盤按下觸發的事件
 3         /// </summary>
 4         /// <param name="sender"></param>
 5         /// <param name="e"></param>
 6         private void hook_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
 7         {
 8            string temp=string.Empty;
 9            DateTime nowTime = DateTime.Now;
10            if ((e.KeyData >= Keys.D0 && e.KeyData <= Keys.D9) || (e.KeyData >= Keys.NumPad0 && e.KeyData <= Keys.NumPad9))//判斷是不是數字的鍵盤值
11                temp = (e.KeyData.ToString()).Last().ToString();
12            else
13                temp = e.KeyData.ToString();
14             if((nowTime-previewTime).Milliseconds<20)//通過判斷鍵盤輸入的間隔來確定是掃描槍還是通過鍵盤輸入的
15             {
16                 if (e.KeyValue == (int)Keys.Return&&inputKey.Length>2)
17                 {
18                     this.tb_Key.Text = inputKey.ToString();
19                     inputKey = new StringBuilder();
20                     previewTime = DateTime.Now;
21                     return;
22                 }
23                 inputKey.Append(temp);  
24               
25             }
26             else
27             {
28                 inputKey=new StringBuilder(temp);
29             }
30             previewTime = DateTime.Now;
31         }

 

因為掃描槍最后掃描完成后會返回一個回車鍵,我們通過回車鍵和每個按鍵的時間間隔去判斷,為了防止同事按下某一個鍵和回車鍵,他們的時間間隔也是很小,所以可能通過最后輸入的字符串的長度去判斷,畢竟條形碼只有一兩個字符的很少吧,看個人的需求了!

總結:

   抓取掃描槍掃描數據實現很簡單,也沒深奧的技術,就是對自己實現功能的一個記錄,也是想讓其他人能夠給指點一下,是否有更准確的方案,畢竟一個pc上不一定只接了一個外設,很多其他的外設可能也會刷入數據,這樣就無法區分是掃描槍還是其他外設的輸入的數據了!如果有更好的建議的希望留言指點,先感謝大家了!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM