每一个键盘都由一个厂商生产,因为每一个键盘安排的扫描码一般都不会一样。这扫描码就是键盘厂商定义的,所以也称为OEM扫描码。微软为了提高兼容性和扩展性,并没有将识别按键的任务给键盘厂商。让各个厂商来协商确定一个标准,这已不是微软要做的事情,而且不会这么做的,没必要。所以微软定义的一套键码就是我们常常听说的虚拟键码。这个虚拟键码就对应着唯一表示这一个按键的意义,而虚拟键码又和OEM扫描码来映射一次。微软提供标准的映射接口,键盘驱动程序扫描得到扫描码之后,向上提供到对应的接口即可。虚拟键码的分析,请参考文章《虚拟键码的所蕴含的Windows系统的设计思想之一》。
因为键盘的按键的扫描码是与按键所在的位置有关的,这通常由电子电路设计来确定的。这样的话,不同的键盘布局,扫描码就不会一样。所以,虚拟键码就避开了这个问题。如果你的程序是直接检测OEM扫描码的话,就与硬件依赖了。如果换成其他的键盘,布局就不一样,那么你的程序就工作不好了。在同一个位置的扫描码都不一样。下面是我的键盘的扫描码图:
【键盘OEM扫描码分布图,只标注了部分】
在图中可以看到,我的键盘的扫描码得到的ESC键的OEM扫描码是1,但是你的键盘就可能不是1了。这个和具体的键盘有关。这就是与硬件相关的了。所以,你想你编写的程序不受具体的键盘硬件种类影响,就只要使用虚拟键码即可。虚拟键码在WM_KEYDOWN和WM_SYSKEYDOWN消息的wParam中携带着。你只要将这个参数与Windows定义的虚拟键码宏比较即可实现与键盘硬件设备无关。只要是键盘,就可以正常的工作。
一般人不懂OEM扫描码自然也用不到扫描码,也不会出现设备相关带来的问题。这是小白的幸福。然而如果你想处理OEM扫描码,也是可以的。只不过一般不推荐使用,Windows中基本上都不会用OEM扫描码来确定按键的。但是如果你确实想使用OEM扫描码为某种键盘编写程序,也是可以的。
在键盘按下的消息的lParam参数中,携带有OEM扫描码的值。OEM扫描码是lParam参数的高字部分的低字节。所以,我们要使用一些宏来帮助我们来提取这个字节,宏的使用有HIWORD和LOBYTE,使用方法请阅读《DWORD、WORD的高低部分提取和合成宏代码示例》。
那么我们就使用如下代码即可提取OEM扫描码:
BYTE btOEM = LOBYTE(HIWORD(lParam));//提取OEM扫描码
提取出来的OEM扫描码和虚拟键码差不多,也是一个数字编码。系统按键需要在WM_SYSKEYDOWN消息中提取,比如F10、Alt等。程序的效果图如下:
【键盘产生的OEM扫描码】
下面是提取OEM扫描码的完整代码:
#include "windows.h"
#include <tchar.h>
#define MAX 100
TCHAR tip[MAX]=_T("");
// - 项目是Unicode字符集
LRESULT CALLBACK WinProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
static int iPosInput=0;
TEXTMETRIC tm;
switch (message)
{
case WM_PAINT:
hdc = BeginPaint(hwnd,&ps);
TextOut(hdc,0,0,tip,lstrlen(tip));
EndPaint(hwnd,&ps);
return 0;
case WM_SYSKEYDOWN:
{
BYTE btOEM = LOBYTE(HIWORD(lParam));
wsprintf(tip,_T("OEM码:%d"),btOEM);
}
InvalidateRect(hwnd,NULL,TRUE);
return 0;
case WM_KEYDOWN:
{
BYTE btOEM = LOBYTE(HIWORD(lParam));
wsprintf(tip,_T("OEM码:%d"),btOEM);
}
InvalidateRect(hwnd,NULL,TRUE);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
break;//跳出到默认处理
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrev,LPSTR lpCmd,int iShow)
{
TCHAR ClassName[] = _T("MyClass");
TCHAR title1[] = _T("C++技术网http://www.cjjjs.com");
WNDCLASS wndClass;
wndClass.cbClsExtra=0;
wndClass.cbWndExtra=0;
wndClass.hbrBackground= (HBRUSH)GetStockObject(WHITE_BRUSH);
wndClass.hCursor=LoadCursor(NULL,IDC_HAND);
wndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
wndClass.hInstance = hInstance;
wndClass.lpfnWndProc = WinProc;
wndClass.lpszClassName = ClassName;
wndClass.lpszMenuName=NULL;
wndClass.style=CS_HREDRAW|CS_VREDRAW;
if(!RegisterClass(&wndClass))return 0;
HWND hwnd = CreateWindow(ClassName,title1,WS_OVERLAPPEDWINDOW,0,0,650,400,NULL,NULL,hInstance,NULL);
ShowWindow(hwnd,SW_SHOWNORMAL);
MSG msg;
while (GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}